Add Codex Home Assistant dashboards
This commit is contained in:
parent
8004d5ca0d
commit
8894fa949c
6 changed files with 619 additions and 1 deletions
53
AGENTS.md
53
AGENTS.md
|
|
@ -32,6 +32,9 @@ Este arquivo deve refletir o estado atual do repo. Se a estrutura mudar, atualiz
|
|||
- `configs/hypr/`: fontes de verdade do Hyprland e asset do wallpaper.
|
||||
- `configs/waybar/`: configs e scripts do Waybar.
|
||||
- `configs/doom/`: configuracao do Doom Emacs versionada no repo.
|
||||
- `configs/home-assistant/`: scripts auxiliares consumidos por servicos do host para alimentar o Home Assistant.
|
||||
- `configs/home-assistant/codex_status.py`: exportador do status local do Codex.
|
||||
- `configs/home-assistant/ui-lovelace.yaml`: dashboard principal do Home Assistant em YAML.
|
||||
- `configs/wofi/`: configuracao e tema do launcher Wofi.
|
||||
- `secrets/secrets.nix`: regras do agenix.
|
||||
- `secrets/*.age`: segredos criptografados.
|
||||
|
|
@ -558,6 +561,56 @@ Itens que um agente deve verificar antes de mexer:
|
|||
- `Home Assistant` local e `~/.config/secrets/ha_token` sao dependencias externas do controle de ar.
|
||||
- `open-webui` depende do `ollama` configurado no host.
|
||||
|
||||
### Home Assistant / Codex status
|
||||
|
||||
Arquivo principal:
|
||||
|
||||
- `configs/home-assistant/codex_status.py`
|
||||
|
||||
Estado atual:
|
||||
|
||||
- O host exporta o status local do Codex a partir de `~/.codex/sessions/.../*.jsonl`.
|
||||
- O export e feito pelo timer `codex-status-export`, que gera `/var/lib/hass/codex_status.json`.
|
||||
- O Home Assistant le esse JSON via sensores `command_line`.
|
||||
- Sensores expostos hoje:
|
||||
- `sensor.codex_5h_left`
|
||||
- `sensor.codex_5h_reset`
|
||||
- `sensor.codex_5h_reset_formatted`
|
||||
- `sensor.codex_weekly_left`
|
||||
- `sensor.codex_weekly_reset`
|
||||
- `sensor.codex_weekly_reset_formatted`
|
||||
|
||||
Cuidados:
|
||||
|
||||
- Nao tente obter esses dados via API de billing da OpenAI sem necessidade; o estado atual usa apenas arquivos locais do Codex.
|
||||
- Se mover a origem dos logs do Codex, ajuste `CODEX_SESSION_ROOT` no servico `codex-status-export`.
|
||||
- O dashboard do Home Assistant continua em storage mode; a fonte declarativa aqui cobre os sensores, nao os cards da UI.
|
||||
|
||||
### Home Assistant / dashboard do Codex
|
||||
|
||||
Arquivo principal:
|
||||
|
||||
- `configs/home-assistant/ui-lovelace.yaml`
|
||||
- `configs/home-assistant/ui-overview.yaml`
|
||||
|
||||
Estado atual:
|
||||
|
||||
- A dashboard principal `Home` e declarativa e vem de `ui-lovelace.yaml`.
|
||||
- Os recursos custom do Lovelace sao declarados em `services.home-assistant.config.lovelace.resources`.
|
||||
- O painel do Codex existe na dashboard principal `Home` e tambem na dashboard extra `Visão Geral`.
|
||||
- O painel escolhido para o Codex e o minimalista.
|
||||
|
||||
Dependencias visuais atuais:
|
||||
|
||||
- `lovelace-mushroom`
|
||||
- `button-card`
|
||||
|
||||
Cuidados:
|
||||
|
||||
- Se algum desses recursos nao estiver instalado no HACS, a dashboard pode abrir com erro de `custom element doesn't exist`.
|
||||
- Mudancas pela UI na dashboard principal nao sao a fonte de verdade; o repo sobrescreve `ui-lovelace.yaml`.
|
||||
- Recursos Lovelace relevantes devem ser mantidos na configuracao declarativa, nao apenas pela UI.
|
||||
|
||||
## Arquivos gerados que NAO devem ser editados diretamente
|
||||
|
||||
- `~/.config/hypr/hyprpaper.conf`
|
||||
|
|
|
|||
88
configs/home-assistant/codex_status.py
Normal file
88
configs/home-assistant/codex_status.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
|
||||
def iso_from_epoch(value):
|
||||
if value is None:
|
||||
return None
|
||||
return datetime.fromtimestamp(float(value), tz=timezone.utc).isoformat()
|
||||
|
||||
|
||||
def clamp_percent(value):
|
||||
value = max(0.0, min(100.0, float(value)))
|
||||
return round(value, 2)
|
||||
|
||||
|
||||
def extract_status(session_root):
|
||||
files = sorted(session_root.rglob("*.jsonl"), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
|
||||
for path in files:
|
||||
try:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
lines = handle.readlines()
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
for line in reversed(lines):
|
||||
try:
|
||||
event = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
if event.get("type") != "event_msg":
|
||||
continue
|
||||
|
||||
payload = event.get("payload") or {}
|
||||
if payload.get("type") != "token_count":
|
||||
continue
|
||||
|
||||
info = payload.get("info") or {}
|
||||
rate_limits = payload.get("rate_limits") or {}
|
||||
|
||||
primary = rate_limits.get("primary") or {}
|
||||
secondary = rate_limits.get("secondary") or {}
|
||||
|
||||
if "used_percent" not in primary or "used_percent" not in secondary:
|
||||
continue
|
||||
|
||||
return {
|
||||
"plan_type": rate_limits.get("plan_type"),
|
||||
"five_hour_left_percent": clamp_percent(100.0 - float(primary["used_percent"])),
|
||||
"five_hour_resets_at": iso_from_epoch(primary.get("resets_at")),
|
||||
"weekly_left_percent": clamp_percent(100.0 - float(secondary["used_percent"])),
|
||||
"weekly_resets_at": iso_from_epoch(secondary.get("resets_at")),
|
||||
"exported_at": datetime.now(timezone.utc).isoformat(),
|
||||
"source_file": str(path),
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
output_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/var/lib/hass/codex_status.json")
|
||||
session_root = Path(os.environ.get("CODEX_SESSION_ROOT", str(Path.home() / ".codex" / "sessions")))
|
||||
|
||||
status = extract_status(session_root)
|
||||
if status is None:
|
||||
print(f"No Codex token_count event found under {session_root}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with NamedTemporaryFile("w", encoding="utf-8", dir=output_path.parent, delete=False) as handle:
|
||||
json.dump(status, handle, ensure_ascii=False, separators=(",", ":"))
|
||||
handle.write("\n")
|
||||
temp_path = Path(handle.name)
|
||||
|
||||
temp_path.replace(output_path)
|
||||
output_path.chmod(0o644)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
175
configs/home-assistant/ui-lovelace.yaml
Normal file
175
configs/home-assistant/ui-lovelace.yaml
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
title: Home
|
||||
views:
|
||||
- path: default_view
|
||||
title: Home
|
||||
type: masonry
|
||||
cards:
|
||||
- type: weather-forecast
|
||||
entity: weather.forecast_casa
|
||||
show_forecast: true
|
||||
forecast_type: daily
|
||||
|
||||
- type: thermostat
|
||||
entity: climate.ar
|
||||
features:
|
||||
- style: icons
|
||||
type: climate-hvac-modes
|
||||
hvac_modes:
|
||||
- "off"
|
||||
- heat
|
||||
- cool
|
||||
- dry
|
||||
- fan_only
|
||||
|
||||
- type: custom:button-card
|
||||
entity: sensor.pixel_7a_battery_level
|
||||
icon: mdi:battery
|
||||
name: Bateria Pixel 7a
|
||||
show_icon: true
|
||||
show_name: true
|
||||
show_state: true
|
||||
show_label: false
|
||||
tap_action:
|
||||
action: none
|
||||
hold_action:
|
||||
action: none
|
||||
state_display: >
|
||||
[[[
|
||||
const value = Math.max(0, Math.min(100, Number(entity.state || 0)));
|
||||
return `${Math.round(value)}%`;
|
||||
]]]
|
||||
custom_fields:
|
||||
bar: >
|
||||
[[[
|
||||
const value = Math.max(0, Math.min(100, Number(entity.state || 0)));
|
||||
let color = '#50fa7b';
|
||||
if (value < 20) color = '#ff5555';
|
||||
else if (value < 50) color = '#ffb86c';
|
||||
return `
|
||||
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||
</div>
|
||||
`;
|
||||
]]]
|
||||
styles:
|
||||
card:
|
||||
- padding: 18px
|
||||
- border-radius: 22px
|
||||
- background: var(--ha-card-background, var(--card-background-color, #1c1c1c))
|
||||
- box-shadow: none
|
||||
grid:
|
||||
- grid-template-areas: '"i n s" "bar bar bar"'
|
||||
- grid-template-columns: min-content 1fr min-content
|
||||
- grid-template-rows: min-content min-content
|
||||
- column-gap: 12px
|
||||
- row-gap: 12px
|
||||
img_cell:
|
||||
- justify-self: start
|
||||
- align-self: center
|
||||
- width: 34px
|
||||
- height: 34px
|
||||
icon:
|
||||
- color: "#50fa7b"
|
||||
- width: 24px
|
||||
- height: 24px
|
||||
name:
|
||||
- justify-self: start
|
||||
- align-self: center
|
||||
- color: white
|
||||
- font-size: 22px
|
||||
- font-weight: 600
|
||||
state:
|
||||
- justify-self: end
|
||||
- align-self: center
|
||||
- color: white
|
||||
- font-size: 18px
|
||||
- font-weight: 700
|
||||
custom_fields:
|
||||
bar:
|
||||
- margin-top: 2px
|
||||
|
||||
- type: custom:button-card
|
||||
entity: sensor.codex_5h_left
|
||||
triggers_update:
|
||||
- sensor.codex_5h_reset_formatted
|
||||
- sensor.codex_weekly_left
|
||||
- sensor.codex_weekly_reset_formatted
|
||||
icon: mdi:robot-outline
|
||||
name: Uso do Codex
|
||||
show_icon: true
|
||||
show_name: true
|
||||
show_state: false
|
||||
show_label: false
|
||||
tap_action:
|
||||
action: none
|
||||
hold_action:
|
||||
action: none
|
||||
custom_fields:
|
||||
fiveh: >
|
||||
[[[
|
||||
const value = Math.max(0, Math.min(100, Number(states['sensor.codex_5h_left']?.state || 0)));
|
||||
const reset = states['sensor.codex_5h_reset_formatted']?.state || 'indisponível';
|
||||
let color = '#50fa7b';
|
||||
if (value < 20) color = '#ff5555';
|
||||
else if (value < 50) color = '#ffb86c';
|
||||
return `
|
||||
<div style="display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:6px;">
|
||||
<span style="color:rgba(248,250,252,0.88);font-size:14px;font-weight:600;">Janela de 5h</span>
|
||||
<span style="color:#ffffff;font-size:16px;font-weight:700;">${Math.round(value)}%</span>
|
||||
</div>
|
||||
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||
</div>
|
||||
<div style="margin-top:6px;color:rgba(226,232,240,0.72);font-size:12px;">Reinicia em ${reset}</div>
|
||||
`;
|
||||
]]]
|
||||
weekly: >
|
||||
[[[
|
||||
const value = Math.max(0, Math.min(100, Number(states['sensor.codex_weekly_left']?.state || 0)));
|
||||
const reset = states['sensor.codex_weekly_reset_formatted']?.state || 'indisponível';
|
||||
let color = '#8be9fd';
|
||||
if (value < 30) color = '#ff5555';
|
||||
else if (value < 65) color = '#ffb86c';
|
||||
return `
|
||||
<div style="display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:6px;">
|
||||
<span style="color:rgba(248,250,252,0.88);font-size:14px;font-weight:600;">Limite semanal</span>
|
||||
<span style="color:#ffffff;font-size:16px;font-weight:700;">${Math.round(value)}%</span>
|
||||
</div>
|
||||
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||
</div>
|
||||
<div style="margin-top:6px;color:rgba(226,232,240,0.72);font-size:12px;">Reinicia em ${reset}</div>
|
||||
`;
|
||||
]]]
|
||||
styles:
|
||||
card:
|
||||
- padding: 18px
|
||||
- border-radius: 22px
|
||||
- background: var(--ha-card-background, var(--card-background-color, #1c1c1c))
|
||||
- box-shadow: none
|
||||
grid:
|
||||
- grid-template-areas: '"i n" "fiveh fiveh" "weekly weekly"'
|
||||
- grid-template-columns: min-content 1fr
|
||||
- grid-template-rows: min-content min-content min-content
|
||||
- column-gap: 12px
|
||||
- row-gap: 14px
|
||||
img_cell:
|
||||
- justify-self: start
|
||||
- align-self: center
|
||||
- width: 34px
|
||||
- height: 34px
|
||||
icon:
|
||||
- color: "#8be9fd"
|
||||
- width: 24px
|
||||
- height: 24px
|
||||
name:
|
||||
- justify-self: start
|
||||
- align-self: center
|
||||
- color: white
|
||||
- font-size: 22px
|
||||
- font-weight: 600
|
||||
custom_fields:
|
||||
fiveh:
|
||||
- margin-top: 4px
|
||||
weekly:
|
||||
- margin-top: 2px
|
||||
175
configs/home-assistant/ui-overview.yaml
Normal file
175
configs/home-assistant/ui-overview.yaml
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
title: Visão Geral
|
||||
views:
|
||||
- path: default_view
|
||||
title: Visão Geral
|
||||
type: masonry
|
||||
cards:
|
||||
- type: weather-forecast
|
||||
entity: weather.forecast_casa
|
||||
show_forecast: true
|
||||
forecast_type: daily
|
||||
|
||||
- type: thermostat
|
||||
entity: climate.ar
|
||||
features:
|
||||
- style: icons
|
||||
type: climate-hvac-modes
|
||||
hvac_modes:
|
||||
- "off"
|
||||
- heat
|
||||
- cool
|
||||
- dry
|
||||
- fan_only
|
||||
|
||||
- type: custom:button-card
|
||||
entity: sensor.pixel_7a_battery_level
|
||||
icon: mdi:battery
|
||||
name: Bateria Pixel 7a
|
||||
show_icon: true
|
||||
show_name: true
|
||||
show_state: true
|
||||
show_label: false
|
||||
tap_action:
|
||||
action: none
|
||||
hold_action:
|
||||
action: none
|
||||
state_display: >
|
||||
[[[
|
||||
const value = Math.max(0, Math.min(100, Number(entity.state || 0)));
|
||||
return `${Math.round(value)}%`;
|
||||
]]]
|
||||
custom_fields:
|
||||
bar: >
|
||||
[[[
|
||||
const value = Math.max(0, Math.min(100, Number(entity.state || 0)));
|
||||
let color = '#50fa7b';
|
||||
if (value < 20) color = '#ff5555';
|
||||
else if (value < 50) color = '#ffb86c';
|
||||
return `
|
||||
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||
</div>
|
||||
`;
|
||||
]]]
|
||||
styles:
|
||||
card:
|
||||
- padding: 18px
|
||||
- border-radius: 22px
|
||||
- background: var(--ha-card-background, var(--card-background-color, #1c1c1c))
|
||||
- box-shadow: none
|
||||
grid:
|
||||
- grid-template-areas: '"i n s" "bar bar bar"'
|
||||
- grid-template-columns: min-content 1fr min-content
|
||||
- grid-template-rows: min-content min-content
|
||||
- column-gap: 12px
|
||||
- row-gap: 12px
|
||||
img_cell:
|
||||
- justify-self: start
|
||||
- align-self: center
|
||||
- width: 34px
|
||||
- height: 34px
|
||||
icon:
|
||||
- color: "#50fa7b"
|
||||
- width: 24px
|
||||
- height: 24px
|
||||
name:
|
||||
- justify-self: start
|
||||
- align-self: center
|
||||
- color: white
|
||||
- font-size: 22px
|
||||
- font-weight: 600
|
||||
state:
|
||||
- justify-self: end
|
||||
- align-self: center
|
||||
- color: white
|
||||
- font-size: 18px
|
||||
- font-weight: 700
|
||||
custom_fields:
|
||||
bar:
|
||||
- margin-top: 2px
|
||||
|
||||
- type: custom:button-card
|
||||
entity: sensor.codex_5h_left
|
||||
triggers_update:
|
||||
- sensor.codex_5h_reset_formatted
|
||||
- sensor.codex_weekly_left
|
||||
- sensor.codex_weekly_reset_formatted
|
||||
icon: mdi:robot-outline
|
||||
name: Uso do Codex
|
||||
show_icon: true
|
||||
show_name: true
|
||||
show_state: false
|
||||
show_label: false
|
||||
tap_action:
|
||||
action: none
|
||||
hold_action:
|
||||
action: none
|
||||
custom_fields:
|
||||
fiveh: >
|
||||
[[[
|
||||
const value = Math.max(0, Math.min(100, Number(states['sensor.codex_5h_left']?.state || 0)));
|
||||
const reset = states['sensor.codex_5h_reset_formatted']?.state || 'indisponível';
|
||||
let color = '#50fa7b';
|
||||
if (value < 20) color = '#ff5555';
|
||||
else if (value < 50) color = '#ffb86c';
|
||||
return `
|
||||
<div style="display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:6px;">
|
||||
<span style="color:rgba(248,250,252,0.88);font-size:14px;font-weight:600;">Janela de 5h</span>
|
||||
<span style="color:#ffffff;font-size:16px;font-weight:700;">${Math.round(value)}%</span>
|
||||
</div>
|
||||
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||
</div>
|
||||
<div style="margin-top:6px;color:rgba(226,232,240,0.72);font-size:12px;">Reinicia em ${reset}</div>
|
||||
`;
|
||||
]]]
|
||||
weekly: >
|
||||
[[[
|
||||
const value = Math.max(0, Math.min(100, Number(states['sensor.codex_weekly_left']?.state || 0)));
|
||||
const reset = states['sensor.codex_weekly_reset_formatted']?.state || 'indisponível';
|
||||
let color = '#8be9fd';
|
||||
if (value < 30) color = '#ff5555';
|
||||
else if (value < 65) color = '#ffb86c';
|
||||
return `
|
||||
<div style="display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:6px;">
|
||||
<span style="color:rgba(248,250,252,0.88);font-size:14px;font-weight:600;">Limite semanal</span>
|
||||
<span style="color:#ffffff;font-size:16px;font-weight:700;">${Math.round(value)}%</span>
|
||||
</div>
|
||||
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||
</div>
|
||||
<div style="margin-top:6px;color:rgba(226,232,240,0.72);font-size:12px;">Reinicia em ${reset}</div>
|
||||
`;
|
||||
]]]
|
||||
styles:
|
||||
card:
|
||||
- padding: 18px
|
||||
- border-radius: 22px
|
||||
- background: var(--ha-card-background, var(--card-background-color, #1c1c1c))
|
||||
- box-shadow: none
|
||||
grid:
|
||||
- grid-template-areas: '"i n" "fiveh fiveh" "weekly weekly"'
|
||||
- grid-template-columns: min-content 1fr
|
||||
- grid-template-rows: min-content min-content min-content
|
||||
- column-gap: 12px
|
||||
- row-gap: 14px
|
||||
img_cell:
|
||||
- justify-self: start
|
||||
- align-self: center
|
||||
- width: 34px
|
||||
- height: 34px
|
||||
icon:
|
||||
- color: "#8be9fd"
|
||||
- width: 24px
|
||||
- height: 24px
|
||||
name:
|
||||
- justify-self: start
|
||||
- align-self: center
|
||||
- color: white
|
||||
- font-size: 22px
|
||||
- font-weight: 600
|
||||
custom_fields:
|
||||
fiveh:
|
||||
- margin-top: 4px
|
||||
weekly:
|
||||
- margin-top: 2px
|
||||
|
|
@ -387,7 +387,7 @@
|
|||
fi
|
||||
|
||||
if [ -d "$dst_dir/$preferred_profile" ]; then
|
||||
python3 - "$dst_dir" "$preferred_profile" <<'PY'
|
||||
${pkgs.python3}/bin/python3 - "$dst_dir" "$preferred_profile" <<'PY'
|
||||
import configparser
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
let
|
||||
username = "ltadeu6";
|
||||
homeDir = "/home/${username}";
|
||||
codexStatusPath = "/var/lib/hass/codex_status.json";
|
||||
zenExtension = shortId: guid: {
|
||||
name = guid;
|
||||
value = {
|
||||
|
|
@ -223,6 +224,11 @@ in {
|
|||
enable = true;
|
||||
openFirewall = true;
|
||||
extraComponents = [ "lg_thinq" ];
|
||||
extraPackages = python3Packages: [
|
||||
python3Packages.getmac
|
||||
python3Packages.pymetno
|
||||
python3Packages.aiogithubapi
|
||||
];
|
||||
config = {
|
||||
homeassistant = {
|
||||
name = "Casa";
|
||||
|
|
@ -231,6 +237,97 @@ in {
|
|||
};
|
||||
|
||||
default_config = { };
|
||||
|
||||
command_line = [
|
||||
{
|
||||
sensor = {
|
||||
name = "Codex 5h Left";
|
||||
unique_id = "codex_5h_left";
|
||||
command = "${pkgs.coreutils}/bin/cat ${codexStatusPath}";
|
||||
value_template = "{{ value_json.five_hour_left_percent }}";
|
||||
unit_of_measurement = "%";
|
||||
scan_interval = 60;
|
||||
icon = "mdi:timer-sand";
|
||||
};
|
||||
}
|
||||
{
|
||||
sensor = {
|
||||
name = "Codex 5h Reset";
|
||||
unique_id = "codex_5h_reset";
|
||||
command = "${pkgs.coreutils}/bin/cat ${codexStatusPath}";
|
||||
value_template = "{{ value_json.five_hour_resets_at }}";
|
||||
device_class = "timestamp";
|
||||
scan_interval = 60;
|
||||
icon = "mdi:timer-refresh";
|
||||
};
|
||||
}
|
||||
{
|
||||
sensor = {
|
||||
name = "Codex Weekly Left";
|
||||
unique_id = "codex_weekly_left";
|
||||
command = "${pkgs.coreutils}/bin/cat ${codexStatusPath}";
|
||||
value_template = "{{ value_json.weekly_left_percent }}";
|
||||
unit_of_measurement = "%";
|
||||
scan_interval = 60;
|
||||
icon = "mdi:calendar-week";
|
||||
};
|
||||
}
|
||||
{
|
||||
sensor = {
|
||||
name = "Codex Weekly Reset";
|
||||
unique_id = "codex_weekly_reset";
|
||||
command = "${pkgs.coreutils}/bin/cat ${codexStatusPath}";
|
||||
value_template = "{{ value_json.weekly_resets_at }}";
|
||||
device_class = "timestamp";
|
||||
scan_interval = 60;
|
||||
icon = "mdi:calendar-refresh";
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
template = [
|
||||
{
|
||||
sensor = [
|
||||
{
|
||||
name = "Codex 5h Reset Formatted";
|
||||
unique_id = "codex_5h_reset_formatted";
|
||||
state =
|
||||
"{{ as_timestamp(states('sensor.codex_5h_reset')) | timestamp_custom('%d/%m/%Y %H:%M', true) }}";
|
||||
icon = "mdi:timer-refresh";
|
||||
}
|
||||
{
|
||||
name = "Codex Weekly Reset Formatted";
|
||||
unique_id = "codex_weekly_reset_formatted";
|
||||
state =
|
||||
"{{ as_timestamp(states('sensor.codex_weekly_reset')) | timestamp_custom('%d/%m/%Y %H:%M', true) }}";
|
||||
icon = "mdi:calendar-refresh";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
lovelace = {
|
||||
dashboards = {
|
||||
overview-dashboard = {
|
||||
mode = "yaml";
|
||||
title = "Visão Geral";
|
||||
icon = "mdi:view-dashboard";
|
||||
show_in_sidebar = true;
|
||||
require_admin = false;
|
||||
filename = "/etc/home-assistant/ui-overview.yaml";
|
||||
};
|
||||
};
|
||||
resources = [
|
||||
{
|
||||
url = "/hacsfiles/lovelace-mushroom/mushroom.js";
|
||||
type = "module";
|
||||
}
|
||||
{
|
||||
url = "/hacsfiles/button-card/button-card.js";
|
||||
type = "module";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
dbus.enable = true;
|
||||
|
|
@ -708,6 +805,12 @@ in {
|
|||
|
||||
users.groups.libvirtd.members = [ username ];
|
||||
|
||||
services.home-assistant.lovelaceConfigFile =
|
||||
../../configs/home-assistant/ui-lovelace.yaml;
|
||||
|
||||
environment.etc."home-assistant/ui-overview.yaml".source =
|
||||
../../configs/home-assistant/ui-overview.yaml;
|
||||
|
||||
users.users.${username} = {
|
||||
isNormalUser = true;
|
||||
description = "Lucas Tadeu";
|
||||
|
|
@ -784,6 +887,30 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
systemd.services.codex-status-export = {
|
||||
description = "Export Codex status for Home Assistant";
|
||||
after = [ "home-assistant.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
export HOME=${homeDir}
|
||||
export CODEX_SESSION_ROOT=${homeDir}/.codex/sessions
|
||||
${pkgs.python3}/bin/python3 ${../../configs/home-assistant/codex_status.py} ${codexStatusPath}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.codex-status-export = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "2min";
|
||||
OnUnitActiveSec = "1min";
|
||||
Persistent = true;
|
||||
Unit = "codex-status-export.service";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers."nix-flake-update" = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue