Something went wrong. Try again.
last activity notifications for opencode
Something went wrong. Try again.
4.6 kB · 148 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149/** * Elapsed-duration formatting. * * Ported from openlastcode `src/time/` (which itself mirrors * opencode-quota-finer's countdown formatting, inverted for elapsed time). * Unit selection and decimal logic are kept identical so output feels * consistent across the three plugins. * * Pure: given (elapsedMs, style, decimals) the same string always comes out. */
/** How an elapsed duration is rendered as text. */export type FormatStyle = "compact" | "verbose" | "precise" | "clock";
export const FORMAT_STYLES: readonly FormatStyle[] = ["compact", "verbose", "precise", "clock"];export const DEFAULT_FORMAT_STYLE: FormatStyle = "precise";
/** Upper bound for the `decimals` setting (precise style). */export const MAX_DECIMALS = 4;export const DEFAULT_DECIMALS = 1;
/** Clamp a decimals value into the accepted [0, MAX_DECIMALS] range. */export function clampDecimals(n: number): number { if (!Number.isFinite(n)) return DEFAULT_DECIMALS; return Math.max(0, Math.min(MAX_DECIMALS, Math.trunc(n)));}
const MS_PER_SECOND = 1_000;const MS_PER_MINUTE = 60 * MS_PER_SECOND;const MS_PER_HOUR = 60 * MS_PER_MINUTE;const MS_PER_DAY = 24 * MS_PER_HOUR;
function pad2(n: number): string { return String(Math.trunc(n)).padStart(2, "0");}
/** * Compact style: a single largest unit, rounded down. * * - 0ms -> "0s" * - 45_000 -> "45s" * - 5m 30s -> "5m" * - 2h 14m -> "2h" * - 1d 3h -> "1d" */export function formatCompact(elapsedMs: number): string { const ms = Math.max(0, Math.trunc(elapsedMs)); if (ms < MS_PER_MINUTE) return `${Math.floor(ms / MS_PER_SECOND)}s`; if (ms < MS_PER_HOUR) return `${Math.floor(ms / MS_PER_MINUTE)}m`; if (ms < MS_PER_DAY) return `${Math.floor(ms / MS_PER_HOUR)}h`; return `${Math.floor(ms / MS_PER_DAY)}d`;}
/** * Verbose style: the two largest non-zero units. * * - 5m 30s -> "5m 30s" * - 2h 14m -> "2h 14m" * - 45s -> "45s" */export function formatVerbose(elapsedMs: number): string { const ms = Math.max(0, Math.trunc(elapsedMs)); const days = Math.floor(ms / MS_PER_DAY); const hours = Math.floor((ms % MS_PER_DAY) / MS_PER_HOUR); const minutes = Math.floor((ms % MS_PER_HOUR) / MS_PER_MINUTE); const seconds = Math.floor((ms % MS_PER_MINUTE) / MS_PER_SECOND);
const parts: string[] = []; if (days > 0) parts.push(`${days}d`); if (hours > 0) parts.push(`${hours}h`); if (minutes > 0) parts.push(`${minutes}m`); if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
return parts.slice(0, 2).join(" ");}
/** * Precise style: the single largest unit as a fraction with `decimals` * decimal places. * * - (5m 24s, 1) -> "5.4m" * - (1h 18m, 2) -> "1.30h" * - (12_300, 1) -> "12.3s" */export function formatPrecise(elapsedMs: number, decimals = DEFAULT_DECIMALS): string { const ms = Math.max(0, Math.trunc(elapsedMs)); const d = clampDecimals(decimals);
let unit: string; let divisor: number; if (ms < MS_PER_MINUTE) { unit = "s"; divisor = MS_PER_SECOND; } else if (ms < MS_PER_HOUR) { unit = "m"; divisor = MS_PER_MINUTE; } else if (ms < MS_PER_DAY) { unit = "h"; divisor = MS_PER_HOUR; } else { unit = "d"; divisor = MS_PER_DAY; }
return `${(ms / divisor).toFixed(d)}${unit}`;}
/** * Clock style: wall-clock time of the activity timestamp. * * `HH:MM` when the activity happened today (local), otherwise `HH:MM DD/MM` * so a stale timestamp stays unambiguous. */export function formatClock(atMs: number, nowMs: number = Date.now()): string { const at = new Date(atMs); const now = new Date(nowMs); const sameDay = at.getFullYear() === now.getFullYear() && at.getMonth() === now.getMonth() && at.getDate() === now.getDate();
const time = `${pad2(at.getHours())}:${pad2(at.getMinutes())}`; if (sameDay) return time; return `${time} ${pad2(at.getDate())}/${pad2(at.getMonth() + 1)}`;}
/** * Render an elapsed duration according to a {@link FormatStyle}. * * For `clock`, pass the activity timestamp (`activityAtMs`) and a `nowMs`; * `elapsedMs` is ignored for that style. */export function formatElapsed( elapsedMs: number, style: FormatStyle, options: { decimals?: number; activityAtMs?: number; nowMs?: number } = {},): string { switch (style) { case "compact": return formatCompact(elapsedMs); case "verbose": return formatVerbose(elapsedMs); case "precise": return formatPrecise(elapsedMs, options.decimals); case "clock": return formatClock(options.activityAtMs ?? Date.now() - elapsedMs, options.nowMs); }}