/** * pi-peak-hours — peak/off-peak billing-hours indicator for LLM APIs. * * Shows one widget line above the editor while any tracked provider is in * its peak billing window (hidden otherwise), plus a `/peak` command with * the full picture: current phase, windows in the local timezone, and the * next flip. Times come from the system clock; the display timezone is * auto-detected and can be overridden in settings (`peakHours.timezone`). * * The widget deliberately does NOT touch the footer: footer replacements * (e.g. pi-archimedes) would hide setStatus() items, while widgets stack * above the editor and coexist with anything. */ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { loadConfig } from "./config.ts"; import { reportLines, resolveTimezone, summarize, widgetSegments, type ProviderStatus } from "./render.ts"; import { SCHEDULES } from "./schedules.ts"; const WIDGET_KEY = "peak-hours"; const TICK_MS = 30_000; export default function peakHours(pi: ExtensionAPI): void { const cfg = loadConfig(); if (!cfg.enabled) return; const schedules = SCHEDULES.filter((s) => cfg.providers.includes(s.id)); if (schedules.length === 0) return; let timer: ReturnType | null = null; let lastCtx: ExtensionContext | null = null; let statuses: ProviderStatus[] = []; let signature = ""; const widgetSignature = (): string => statuses.map((s) => `${s.schedule.id}:${s.peak ? 1 : 0}`).join(",") + `|${cfg.show}`; const refreshWidget = (): void => { const ctx = lastCtx; if (!ctx) return; statuses = summarize(schedules, new Date()); const sig = widgetSignature(); if (sig === signature) return; signature = sig; try { ctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => ({ render: () => { const tz = resolveTimezone(cfg.timezone); const segments = widgetSegments(statuses, new Date(), tz, cfg.show); return segments ? [segments.map((s) => theme.fg(s.tone, s.text)).join("")] : []; }, invalidate() {}, })); } catch { // setWidget is unavailable outside interactive UI modes — /peak still works. } }; pi.on("session_start", (_event, ctx) => { if (timer) return; lastCtx = ctx; refreshWidget(); timer = setInterval(refreshWidget, TICK_MS); timer.unref(); }); pi.on("model_select", (_event, ctx) => { lastCtx = ctx; refreshWidget(); }); pi.on("session_shutdown", () => { if (timer) { clearInterval(timer); timer = null; } try { lastCtx?.ui.setWidget(WIDGET_KEY, undefined); } catch { // UI already gone } lastCtx = null; }); pi.registerCommand("peak", { description: "Peak/off-peak billing hours for tracked LLM providers (DeepSeek, GLM Coding Plan). Config: peakHours in settings.json", handler: async (_args, ctx) => { const now = new Date(); const tz = resolveTimezone(cfg.timezone); const lines = reportLines(summarize(schedules, now), now, tz); if (ctx.hasUI) { ctx.ui.notify(lines.join("\n"), "info"); } else { console.log(lines.join("\n")); } }, }); }