Files
codex-limit-monitor-waybar/bin/codex-waybar-configure
2026-06-15 19:45:16 +03:30

125 lines
3.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Safely add the Codex usage module to an existing Waybar JSONC config."""
from __future__ import annotations
import argparse
import re
import shutil
import sys
from datetime import datetime
from pathlib import Path
MODULE_NAME = "custom/codex-usage"
MODULE_BLOCK = ''' "custom/codex-usage": {
"exec": "codex-waybar",
"return-type": "json",
"interval": 60,
"tooltip": true
}
'''
CSS_BLOCK = """
/* Codex usage module */
#custom-codex-usage {
min-width: 12px;
margin: 0 7.5px;
}
#custom-codex-usage.warning {
color: #d8a657;
}
#custom-codex-usage.critical,
#custom-codex-usage.unavailable {
color: #a55555;
}
#custom-codex-usage.stale {
opacity: 0.65;
}
"""
class ConfigureError(RuntimeError):
pass
def backup(path: Path) -> Path:
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
destination = path.with_name(f"{path.name}.bak.{stamp}")
shutil.copy2(path, destination)
return destination
def insert_module_reference(text: str, section: str) -> str:
pattern = re.compile(rf'("modules-{section}"\s*:\s*\[)(.*?)(\])', re.DOTALL)
matches = list(pattern.finditer(text))
if len(matches) != 1:
raise ConfigureError(f'Could not uniquely locate "modules-{section}"')
match = matches[0]
if f'"{MODULE_NAME}"' in match.group(2):
return text
return text[: match.end(1)] + f'"{MODULE_NAME}", ' + text[match.end(1) :]
def insert_module_block(text: str) -> str:
if re.search(rf'"{re.escape(MODULE_NAME)}"\s*:', text):
return text
close = text.rfind("}")
if close < 0:
raise ConfigureError("Could not locate the top-level closing brace")
before = text[:close].rstrip()
comma = "" if before.endswith("{") else ","
return f"{before}{comma}\n{MODULE_BLOCK}{text[close:]}"
def configure(config_path: Path, style_path: Path, section: str) -> tuple[Path | None, Path | None]:
if not config_path.is_file():
raise ConfigureError(f"Waybar config not found: {config_path}")
original = config_path.read_text()
updated = insert_module_block(insert_module_reference(original, section))
config_backup = None
if updated != original:
config_backup = backup(config_path)
config_path.write_text(updated)
style_backup = None
if style_path.exists():
style = style_path.read_text()
if "#custom-codex-usage" not in style:
style_backup = backup(style_path)
style_path.write_text(style.rstrip() + CSS_BLOCK)
else:
style_path.parent.mkdir(parents=True, exist_ok=True)
style_path.write_text(CSS_BLOCK.lstrip())
return config_backup, style_backup
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
default_dir = Path.home() / ".config" / "waybar"
parser.add_argument("--section", choices=("left", "center", "right"), required=True)
parser.add_argument("--config", type=Path, default=default_dir / "config.jsonc")
parser.add_argument("--style", type=Path, default=default_dir / "style.css")
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
config_backup, style_backup = configure(args.config, args.style, args.section)
except (OSError, ConfigureError) as exc:
print(f"codex-waybar-configure: {exc}", file=sys.stderr)
return 1
print(f"Configured {args.config}")
if config_backup:
print(f"Backup: {config_backup}")
if style_backup:
print(f"Backup: {style_backup}")
return 0
if __name__ == "__main__":
sys.exit(main())