feat(core): display quota renewal date

Render subscription renewal dates and countdowns in quota output, normalize numeric period-end timestamps, and cover UTC and edge-case behavior.
This commit is contained in:
José Antonio Galiano Sandoval
2026-08-26 09:50:18 -06:00
parent 6f367515d8
commit d60934ebdc
3 changed files with 96 additions and 8 deletions
+35 -3
View File
@@ -49,10 +49,42 @@ function creditsDetail(credits: CommandCodeCredits | null): string | undefined {
return `Sources: ${parts.join(" / ")}` return `Sources: ${parts.join(" / ")}`
} }
function subscriptionLine(subscription: CommandCodeSubscription): string { function parsePeriodEnd(value: string): Date | null {
const trimmed = value.trim()
const timestamp = /^\d+$/.test(trimmed) ? Number(trimmed) : Date.parse(trimmed)
if (!Number.isFinite(timestamp) || timestamp < 0) return null
const milliseconds = timestamp >= 1e12 ? timestamp : timestamp * 1000
const date = new Date(milliseconds)
return Number.isNaN(date.getTime()) ? null : date
}
function subscriptionLine(
subscription: CommandCodeSubscription,
now: () => number = Date.now,
): string {
const plan = (subscription.planId ?? "Unknown").replace(/[_-]+/g, " ").trim() const plan = (subscription.planId ?? "Unknown").replace(/[_-]+/g, " ").trim()
const status = subscription.status ? ` (${subscription.status})` : "" const status = subscription.status ? ` (${subscription.status})` : ""
return `Plan: ${plan}${status}` let renewal = ""
if (subscription.currentPeriodEnd) {
const end = parsePeriodEnd(subscription.currentPeriodEnd)
if (end) {
const diffMs = end.getTime() - now()
const days = Math.ceil(diffMs / 86_400_000)
const dateStr = end.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
timeZone: "UTC",
})
if (days > 0) {
renewal = ` · renews ${dateStr} (${days}d)`
} else if (days === 0) {
renewal = ` · renews ${dateStr} (today)`
} else {
renewal = ` · renewed ${dateStr}`
}
}
}
return `Plan: ${plan}${status}${renewal}`
} }
function formatTokens(tokens: number): string { function formatTokens(tokens: number): string {
@@ -77,7 +109,7 @@ export function formatQuota(quota: CommandCodeQuota, now: () => number = Date.no
const detail = creditsDetail(quota.credits) const detail = creditsDetail(quota.credits)
if (detail) lines.push(detail) if (detail) lines.push(detail)
if (quota.subscription) lines.push(subscriptionLine(quota.subscription)) if (quota.subscription) lines.push(subscriptionLine(quota.subscription, now))
if (quota.summary) { if (quota.summary) {
lines.push("") lines.push("")
+9 -2
View File
@@ -43,6 +43,13 @@ function stringValue(value: unknown): string | undefined {
return typeof value === "string" && value.length > 0 ? value : undefined return typeof value === "string" && value.length > 0 ? value : undefined
} }
function timestampValue(value: unknown): string | undefined {
const text = stringValue(value)
if (text) return text
const number = numberValue(value)
return number === undefined ? undefined : String(number)
}
function errorMessage(error: unknown): string { function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error) return error instanceof Error ? error.message : String(error)
} }
@@ -100,8 +107,8 @@ function parseSubscription(value: unknown): CommandCodeSubscription | null {
const data = value.data const data = value.data
const planId = stringValue(data.planId) const planId = stringValue(data.planId)
const status = stringValue(data.status) const status = stringValue(data.status)
const currentPeriodStart = stringValue(data.currentPeriodStart) const currentPeriodStart = timestampValue(data.currentPeriodStart)
const currentPeriodEnd = stringValue(data.currentPeriodEnd) const currentPeriodEnd = timestampValue(data.currentPeriodEnd)
if (!planId && !status && !currentPeriodStart && !currentPeriodEnd) return null if (!planId && !status && !currentPeriodStart && !currentPeriodEnd) return null
return { return {
planId: planId ?? null, planId: planId ?? null,
+52 -3
View File
@@ -276,7 +276,13 @@ describe("Command Code quota", () => {
const { fetchImpl } = okFetch({ const { fetchImpl } = okFetch({
whoami: { user: { userName: "alice", keyName: "Pi Agent" }, org: null }, whoami: { user: { userName: "alice", keyName: "Pi Agent" }, org: null },
credits: { credits: { monthlyCredits: 5, purchasedCredits: 0, freeCredits: 0 } }, credits: { credits: { monthlyCredits: 5, purchasedCredits: 0, freeCredits: 0 } },
subscriptions: { data: { planId: "pro", status: "active" } }, subscriptions: {
data: {
planId: "pro",
status: "active",
currentPeriodEnd: Date.parse("2026-02-01T00:00:00Z"),
},
},
summary: { totalCost: 1.06, totalCount: 654, totalTokens: 74_200_000 }, summary: { totalCost: 1.06, totalCount: 654, totalTokens: 74_200_000 },
}) })
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl }) const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
@@ -284,6 +290,7 @@ describe("Command Code quota", () => {
if (!result.ok) return if (!result.ok) return
assert.equal(result.quota.summary?.totalTokens, 74_200_000) assert.equal(result.quota.summary?.totalTokens, 74_200_000)
assert.equal(result.quota.account.keyName, "Pi Agent") assert.equal(result.quota.account.keyName, "Pi Agent")
assert.equal(result.quota.subscription?.currentPeriodEnd, "1769904000000")
}) })
it("rejects missing API keys as a config error", async () => { it("rejects missing API keys as a config error", async () => {
@@ -327,13 +334,13 @@ describe("Command Code quota", () => {
summary: { totalCost: 12.34, totalCount: 1500 }, summary: { totalCost: 12.34, totalCount: 1500 },
} }
const output = formatQuota(quota, () => 1_700_000_000_000) const output = formatQuota(quota, () => Date.parse("2026-01-15T00:00:00Z"))
assert.doesNotMatch(output, /Command Code quota —/) assert.doesNotMatch(output, /Command Code quota —/)
assert.match(output, /Credits/) assert.match(output, /Credits/)
assert.match(output, /Remaining: \$55\.00 of \$67\.34/) assert.match(output, /Remaining: \$55\.00 of \$67\.34/)
assert.match(output, /Used: \$12\.34/) assert.match(output, /Used: \$12\.34/)
assert.match(output, /Sources: monthly \$40\.00 \/ purchased \$10\.00 \/ free \$5\.00/) assert.match(output, /Sources: monthly \$40\.00 \/ purchased \$10\.00 \/ free \$5\.00/)
assert.match(output, /Plan: pro \(active\)/) assert.match(output, /Plan: pro \(active\) · renews Feb 1 \(17d\)/)
assert.match(output, /Usage \(billing period\)/) assert.match(output, /Usage \(billing period\)/)
assert.match(output, /Cost: \$12\.34/) assert.match(output, /Cost: \$12\.34/)
assert.match(output, /Requests: 1,500/) assert.match(output, /Requests: 1,500/)
@@ -344,6 +351,48 @@ describe("Command Code quota", () => {
assert.match(output, /https:\/\/commandcode\.ai\/usage/) assert.match(output, /https:\/\/commandcode\.ai\/usage/)
}) })
it("formats renewal dates in UTC and handles renewal edge cases", () => {
const baseQuota: CommandCodeQuota = {
account: { login: "alice", orgId: null },
credits: null,
subscription: null,
summary: null,
}
const formatRenewal = (currentPeriodEnd: string | null, now: string) =>
formatQuota(
{
...baseQuota,
subscription: {
planId: "pro",
status: "active",
currentPeriodStart: null,
currentPeriodEnd,
},
},
() => Date.parse(now),
)
const beforeReset = formatRenewal("2026-02-01T00:00:00Z", "2026-01-31T12:00:00Z")
assert.match(beforeReset, /Plan: pro \(active\) · renews Feb 1 \(1d\)/)
const numericTimestamp = formatRenewal(
String(Date.parse("2026-02-01T00:00:00Z")),
"2026-01-31T12:00:00Z",
)
assert.match(numericTimestamp, /Plan: pro \(active\) · renews Feb 1 \(1d\)/)
const today = formatRenewal("2026-01-31T12:00:00Z", "2026-01-31T12:00:00Z")
assert.match(today, /Plan: pro \(active\) · renews Jan 31 \(today\)/)
const expired = formatRenewal("2026-01-30T00:00:00Z", "2026-01-31T12:00:00Z")
assert.match(expired, /Plan: pro \(active\) · renewed Jan 30/)
for (const currentPeriodEnd of [null, "not-a-date"]) {
const output = formatRenewal(currentPeriodEnd, "2026-01-31T12:00:00Z")
assert.doesNotMatch(output, /renews|renewed/)
}
})
it("redacts token-like values from error messages", () => { it("redacts token-like values from error messages", () => {
// 16+ char run after a credential key is redacted by the shared redactor. // 16+ char run after a credential key is redacted by the shared redactor.
assert.equal(redactValue("api_key=abcdefghijklmnop123456"), "api_key=[redacted]") assert.equal(redactValue("api_key=abcdefghijklmnop123456"), "api_key=[redacted]")