Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![node](https://img.shields.io/badge/node-%E2%89%A5%2020-339933?style=flat-square)](./package.json)
[![MIT](https://img.shields.io/badge/license-MIT-007ec6?style=flat-square)](./LICENSE)

<img src="assets/hero.svg" alt="omp-statusline rendering four lines beneath the editor in a terminal: clock, Hijri date and city; the five prayer times with a green countdown on the next prayer; API spend and token volume per window; and z.ai quota pace per window with over/under indicators." width="866">
<img src="assets/hero.svg" alt="omp-statusline rendering four lines beneath the editor in a terminal: clock, Hijri date with Gregorian gloss and city; the five prayer times with a green countdown on the next prayer; API spend and token volume per window; and z.ai quota pace per window with over/under indicators." width="866">

A statusline widget for [omp](https://github.com/can1357/oh-my-pi) (Oh My Pi) — the omp-native successor to [pi-statusline](https://github.com/getpipher/pi-statusline). It renders below the editor in omp's own visual language (` · ` separators, nerd glyphs, theme tokens), and omp's native statusline closes the block beneath it (extension widgets cannot render under it yet — tracked upstream as [can1357/oh-my-pi#11100](https://github.com/can1357/oh-my-pi/issues/11100)).

Expand All @@ -18,7 +18,7 @@ Render order (top → bottom): **info · prayers · money · zai**.

| Line | Contents |
|---|---|
| **info** | Local clock · Hijri date · city — all dim. |
| **info** | Local clock · Hijri date with Gregorian gloss — `27 Rabīʿ al-awwal 1448 (09 Sep 2026)` · city — all dim. |
| **prayers** | All five prayers with wall times, from [aladhan](https://api.aladhan.com) (cached per local day, stale-marker on degradation). Past prayers get a dim `✓`; the next prayer is green with a countdown on its segment only — `Dhuhr 11:51 (3h 36m)`. |
| **money** | API spend + token volume per window: `REPO $68.36 (1.3B)` (all-time for the current project) · `DAY` (since 00:00 local) · `7DAY` / `30DAY` (rolling, hour-aligned). Token volumes are dim, from the same sessions scan. |
| **zai** | Quota pace per window: `LABEL usage%/elapsed% (pace · reset · absolute)` — e.g. `5hrs 16%/26% (30m under · 3h 43m · 11:58)`. **Provider-gated**: renders only while the active model's provider is `zai` (or the provider is unreadable); vanishes entirely on other providers. |
Expand Down Expand Up @@ -79,7 +79,7 @@ State (deen cache) lives beside the config in `~/.omp/agent/omp-statusline/`.
Forces a zai + deen + money refresh and notifies full source state: quota freshness, today's plan credits, 7-day per-model split, usage streaks, cache-hit rate, prayer city/hijri, and the spend breakdown including subagent share and entry count:

```
zai key omp credentials · 5h 16% · weekly 24% · fetched 0m ago · today 7.7K · models 7d glm-5.3 28K · gpt-5.4 9.2K · streak 46d (best 61d) · cache 71% | deen Jakarta · 25 Rabīʿ al-awwal 1448 · fresh | money REPO $68.36 · DAY $26.50 · 7DAY $315.27 · 30DAY $492.88 · sub $11.02 · 1234 entries
zai key omp credentials · 5h 16% · weekly 24% · fetched 0m ago · today 7.7K · models 7d glm-5.3 28K · gpt-5.4 9.2K · streak 46d (best 61d) · cache 71% | deen Jakarta · 25 Rabīʿ al-awwal 1448 (07 Sep 2026) · fresh | money REPO $68.36 · DAY $26.50 · 7DAY $315.27 · 30DAY $492.88 · sub $11.02 · 1234 entries
```

The z.ai dashboard endpoints (`credit-usage/usage-detail`, `credit-usage/activity`) accept the same API key as the quota API — no browser or cookie session needed.
Expand Down
2 changes: 1 addition & 1 deletion assets/hero.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@getpipher/omp-statusline",
"version": "0.5.0",
"version": "0.5.1",
"description": "Four-line omp statusline widget: zai coding-plan quota (provider-gated), prayer times, hijri clock, and API-spend ledger (REPO/DAY/7DAY/30DAY, subagent-inclusive).",
"keywords": [
"pi-package",
Expand Down
8 changes: 8 additions & 0 deletions src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,11 @@ export function formatResetAbs(targetMs: number, now: number): string {
t.getDate() === n.getDate();
return sameDay ? formatClock(targetMs) : `${MONTHS[t.getMonth()]} ${String(t.getDate()).padStart(2, "0")} ${formatClock(targetMs)}`;
}

// v0.5.1: Gregorian gloss beside the Hijri date in infoLine — `09 Sep 2026`.
// Machine-local like the clock; reuses MONTHS for deterministic EN rendering
// (no Intl/platform variance).
export function formatGregorian(ts: number): string {
const d = new Date(ts);
return `${String(d.getDate()).padStart(2, "0")} ${MONTHS[d.getMonth()]} ${d.getFullYear()}`;
}
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// omp-statusline — 4-line belowEditor widget, RECTOR-approved 2026-09-07:
// 󰚯 zai 5hrs 16%/26% (30m under · 3h 43m · 11:58) · 7DAY 33%/31% (3h 22m over · 4d 19h · Sep 12 03:30) ← provider-gated (zai only)
// 󰣎 Fajr 04:33 ✓ · Dhuhr 11:51 (3h 36m) · Asr 15:07 · Maghrib 17:52 · Isha 19:01
// 󰥔 08:15 · 25 Rabīʿ al-awwal 1448 · Jakarta
// 󰥔 08:15 · 25 Rabīʿ al-awwal 1448 (07 Sep 2026) · Jakarta
// 󰄬 REPO $68.36 · DAY $26.50 · 7DAY $315.27 · 30DAY $492.88
// Data layer vendored from @getpipher/pi-statusline (quota/zai, format, deen, adapters);
// money comes from the omp sessions disk-scan (money.ts — subagent-inclusive). State
Expand All @@ -14,7 +14,7 @@ import { readFileSync, mkdirSync } from "node:fs";

import { fetchQuota, readZaiKey, type QuotaLimit, type QuotaResult } from "./quota/zai.ts";
import { FIVE_HOUR_MS, WEEK_MS, windowElapsedPercent } from "./quota/project.ts";
import { formatReset, formatResetAbs } from "./format.ts";
import { formatReset, formatResetAbs, formatGregorian } from "./format.ts";
import { createDeenSource, type DeenSnapshot, type DeenSourceConfig } from "./deen/source.ts";
import { zaiStatusDetail } from "./adapters/zai.ts";
import { scanMoney, type MoneySnapshot } from "./money.ts";
Expand Down Expand Up @@ -215,7 +215,7 @@ export function prayerLine(theme: SlTheme, s: DeenSnapshot, sep: string): string
export function infoLine(theme: SlTheme, s: DeenSnapshot, now: number, sep: string): string {
const d = new Date(now);
const clock = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
return ` ${theme.fg("dim", "󰥔")} ${[clock, s.hijri, s.city].map((part) => theme.fg("dim", part)).join(sep)}`;
return ` ${theme.fg("dim", "󰥔")} ${[clock, `${s.hijri} (${formatGregorian(now)})`, s.city].map((part) => theme.fg("dim", part)).join(sep)}`;
}


Expand Down Expand Up @@ -365,7 +365,7 @@ export default function ompStatusline(pi: SlApi): void {
report && report.cacheHitRate !== null ? `cache ${Math.round(report.cacheHitRate * 100)}%` : "",
].filter(Boolean).join(" · ")
: resolved ? "no quota data" : "no key";
const deenPart = s ? `${s.city} · ${s.hijri}${s.staleMinutes !== null ? ` · stale ${s.staleMinutes}m` : " · fresh"}` : "no data";
const deenPart = s ? `${s.city} · ${s.hijri} (${formatGregorian(Date.now())})${s.staleMinutes !== null ? ` · stale ${s.staleMinutes}m` : " · fresh"}` : "no data";
const moneyPart = `REPO $${money.repo.toFixed(2)} · DAY $${money.day.toFixed(2)} · 7DAY $${money.week.toFixed(2)} · 30DAY $${money.month.toFixed(2)} · sub $${money.sub.toFixed(2)} · ${money.entries} entries`;
cmdCtx.ui.notify(`zai ${zaiPart} | deen ${deenPart} | money ${moneyPart}`, "info");
},
Expand Down
13 changes: 10 additions & 3 deletions test/lines.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { SlTheme } from "../src/index.ts";
import type { DeenSnapshot } from "../src/deen/source.ts";
import type { PrayerScheduleEntry } from "../src/deen/time.ts";
import type { QuotaResult } from "../src/quota/zai.ts";
import { formatResetAbs } from "../src/format.ts";
import { formatResetAbs, formatGregorian } from "../src/format.ts";

const theme: SlTheme = { fg: (token, text) => `<${token}>${text}</>` };
const sep = theme.fg("dim", " · ");
Expand Down Expand Up @@ -77,6 +77,13 @@ test("formatResetAbs: same-day clock, cross-day month-day, past → now", () =>
assert.equal(formatResetAbs(lateNight + 2 * HOUR, lateNight), "Sep 05 01:00"); // crosses midnight → padded single-digit day
});

test("formatGregorian: zero-padded day, EN month from local date, year", () => {
assert.equal(formatGregorian(new Date(2026, 8, 7).getTime()), "07 Sep 2026"); // single-digit day padded
assert.equal(formatGregorian(new Date(2026, 8, 20).getTime()), "20 Sep 2026"); // double-digit day verbatim
assert.equal(formatGregorian(new Date(2026, 11, 31).getTime()), "31 Dec 2026"); // last month of year
assert.equal(formatGregorian(new Date(2027, 0, 1).getTime()), "01 Jan 2027"); // year boundary
});

test("paceText: gap = window × Δ%/100; formats and over/under tokens", () => {
const H = 3_600_000;
assert.deepEqual(paceText(5 * H, 34, 74), { text: "2h 0m under", token: "success" }); // RECTOR's worked example
Expand All @@ -93,11 +100,11 @@ test("heat: band boundaries are >= on both thresholds", () => {
assert.equal(heat(90), "error");
});

test("infoLine: clock · hijri · city, all dim", () => {
test("infoLine: clock · hijri (gregorian in parens) · city, all dim", () => {
const now = new Date(2026, 8, 7, 8, 15, 3).getTime(); // local-time construction → 08:15 in any tz
assert.equal(
infoLine(theme, deen(SCHEDULE), now, sep),
" <dim>󰥔</> <dim>08:15</><dim> · </><dim>25 Rabīʿ al-awwal 1448</><dim> · </><dim>Jakarta</>",
" <dim>󰥔</> <dim>08:15</><dim> · </><dim>25 Rabīʿ al-awwal 1448 (07 Sep 2026)</><dim> · </><dim>Jakarta</>",
);
});

Expand Down
Loading