Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

dashcc

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dashcc - npm Package Compare versions

Comparing version
0.1.8
to
0.1.9
+3
-5
package.json
{
"name": "dashcc",
"version": "0.1.8",
"version": "0.1.9",
"description": "A session dashboard status line for Claude Code CLI",

@@ -8,4 +8,3 @@ "module": "src/ccstatusline.ts",

"bin": {
"dashcc": "dist/ccstatusline.js",
"cdxusage": "dist/cdxusage.js"
"dashcc": "dist/ccstatusline.js"
},

@@ -16,5 +15,4 @@ "files": [

"scripts": {
"cdxusage": "bun run src/cdxusage.ts",
"start": "bun run src/ccstatusline.ts",
"build": "rm -rf dist/* ; bun build src/ccstatusline.ts --target=node --outfile=dist/ccstatusline.js --target-version=14 ; bun build src/cdxusage.ts --target=node --outfile=dist/cdxusage.js --target-version=14",
"build": "rm -rf dist/* ; bun build src/ccstatusline.ts --target=node --outfile=dist/ccstatusline.js --target-version=14",
"postbuild": "bun run scripts/replace-version.ts",

@@ -21,0 +19,0 @@ "example": "cat scripts/payload.example.json | bun start",

#!/usr/bin/env node
// src/cdxusage/dates.ts
function getFormatterPart(timestamp, timezone, partType) {
const formatter = new Intl.DateTimeFormat("en-CA", {
timeZone: timezone,
year: "numeric",
month: "2-digit",
day: "2-digit"
});
const parts = formatter.formatToParts(new Date(timestamp));
return parts.find((part) => part.type === partType)?.value ?? "";
}
function getDateKey(timestamp, timezone) {
const year = getFormatterPart(timestamp, timezone, "year");
const month = getFormatterPart(timestamp, timezone, "month");
const day = getFormatterPart(timestamp, timezone, "day");
return `${year}-${month}-${day}`;
}
function getMonthKey(timestamp, timezone) {
const year = getFormatterPart(timestamp, timezone, "year");
const month = getFormatterPart(timestamp, timezone, "month");
return `${year}-${month}`;
}
function formatTimestamp(timestamp, timezone, locale) {
return new Intl.DateTimeFormat(locale, {
dateStyle: "medium",
timeStyle: "short",
timeZone: timezone
}).format(new Date(timestamp));
}
function normalizeDayInput(input) {
const trimmed = input.trim();
const compactMatch = /^(\d{4})(\d{2})(\d{2})$/.exec(trimmed);
if (compactMatch) {
return `${compactMatch[1]}-${compactMatch[2]}-${compactMatch[3]}`;
}
return /^\d{4}-\d{2}-\d{2}$/.test(trimmed) ? trimmed : null;
}
function normalizeMonthInput(input) {
const trimmed = input.trim();
const compactMatch = /^(\d{4})(\d{2})$/.exec(trimmed);
if (compactMatch) {
return `${compactMatch[1]}-${compactMatch[2]}`;
}
return /^\d{4}-\d{2}$/.test(trimmed) ? trimmed : null;
}
function getMonthBounds(monthKey) {
const match = /^(\d{4})-(\d{2})$/.exec(monthKey);
if (!match) {
throw new Error(`Invalid month key: ${monthKey}`);
}
const yearPart = match[1];
const monthPart = match[2];
if (!yearPart || !monthPart) {
throw new Error(`Invalid month key: ${monthKey}`);
}
const year = Number.parseInt(yearPart, 10);
const month = Number.parseInt(monthPart, 10);
const nextMonth = new Date(Date.UTC(year, month, 1));
const lastDay = new Date(nextMonth.getTime() - 24 * 60 * 60 * 1000);
const until = lastDay.toISOString().slice(0, 10);
return {
since: `${yearPart}-${monthPart}-01`,
until
};
}
// src/cdxusage/format.ts
function formatInteger(value) {
return new Intl.NumberFormat("en-US").format(value);
}
function formatCompactNumber(value) {
return new Intl.NumberFormat("en-US", {
notation: "compact",
maximumFractionDigits: 1
}).format(value);
}
function formatTokenValue(value, compact) {
return compact ? formatCompactNumber(value) : formatInteger(value);
}
function formatUsd(value, hasUnpricedUsage) {
if (value <= 0 && hasUnpricedUsage) {
return "n/a";
}
const formatted = `$${value.toFixed(2)}`;
return hasUnpricedUsage ? `${formatted}+` : formatted;
}
function renderTable(rows, columns) {
const widths = columns.map((column) => {
const valueWidths = rows.map((row) => column.getValue(row).length);
return Math.max(column.header.length, ...valueWidths);
});
const header = columns.map((column, index) => padCell(column.header, widths[index] ?? column.header.length, column.align)).join(" ");
const divider = columns.map((_, index) => "-".repeat(widths[index] ?? 0)).join(" ");
const body = rows.map((row) => columns.map((column, index) => padCell(column.getValue(row), widths[index] ?? column.header.length, column.align)).join(" ")).join(`
`);
return [header, divider, body].filter(Boolean).join(`
`);
}
function padCell(value, width, align = "left") {
return align === "right" ? value.padStart(width) : value.padEnd(width);
}
function buildSummary(report) {
const lines = [
`cdxusage ${report.reportType} report`,
`Source: ${report.codexHome}`,
`Pricing: API equivalent USD (embedded GPT-5 Codex rates)`,
`Rows: ${report.rows.length} | Session files: ${report.sessionFiles}`
];
if (report.since || report.until) {
lines.push(`Range: ${report.since ?? "min"} -> ${report.until ?? "max"} (${report.timezone})`);
} else {
lines.push(`Timezone: ${report.timezone}`);
}
if (report.latestRateLimits) {
const primary = report.latestRateLimits.primaryUsedPercent;
const secondary = report.latestRateLimits.secondaryUsedPercent;
const parts = [
report.latestRateLimits.planType ? `plan=${report.latestRateLimits.planType}` : null,
primary !== null ? `5h=${primary.toFixed(1)}%` : null,
secondary !== null ? `7d=${secondary.toFixed(1)}%` : null
].filter(Boolean);
if (parts.length > 0) {
lines.push(`Latest limits: ${parts.join(" | ")}`);
}
}
return lines;
}
function buildDailyOrMonthlyColumns(report) {
const compact = report.compact;
if (compact) {
return [
{ header: report.reportType === "monthly" ? "Month" : "Date", getValue: (row) => row.label },
{ header: "Sessions", getValue: (row) => String(row.sessionCount), align: "right" },
{ header: "Input", getValue: (row) => formatTokenValue(row.usage.inputTokens, true), align: "right" },
{ header: "Output", getValue: (row) => formatTokenValue(row.usage.outputTokens + row.usage.reasoningOutputTokens, true), align: "right" },
{ header: "Cost", getValue: (row) => formatUsd(row.costUsd, row.hasUnpricedUsage), align: "right" }
];
}
return [
{ header: report.reportType === "monthly" ? "Month" : "Date", getValue: (row) => row.label },
{ header: "Sessions", getValue: (row) => String(row.sessionCount), align: "right" },
{ header: "Models", getValue: (row) => row.models.join(", ") || "unknown" },
{ header: "Input", getValue: (row) => formatTokenValue(row.usage.inputTokens, false), align: "right" },
{ header: "Cached", getValue: (row) => formatTokenValue(row.usage.cachedInputTokens, false), align: "right" },
{ header: "Output", getValue: (row) => formatTokenValue(row.usage.outputTokens, false), align: "right" },
{ header: "Reason", getValue: (row) => formatTokenValue(row.usage.reasoningOutputTokens, false), align: "right" },
{ header: "Total", getValue: (row) => formatTokenValue(row.usage.totalTokens, false), align: "right" },
{ header: "Cost", getValue: (row) => formatUsd(row.costUsd, row.hasUnpricedUsage), align: "right" }
];
}
function buildSessionColumns(report) {
const compact = report.compact;
if (compact) {
return [
{ header: "Session", getValue: (row) => row.sessionShortId ?? row.label },
{ header: "Dir", getValue: (row) => row.sessionRelativeDir ?? "." },
{ header: "Input", getValue: (row) => formatTokenValue(row.usage.inputTokens, true), align: "right" },
{ header: "Output", getValue: (row) => formatTokenValue(row.usage.outputTokens + row.usage.reasoningOutputTokens, true), align: "right" },
{ header: "Cost", getValue: (row) => formatUsd(row.costUsd, row.hasUnpricedUsage), align: "right" }
];
}
return [
{ header: "Session", getValue: (row) => row.sessionShortId ?? row.label },
{ header: "Dir", getValue: (row) => row.sessionRelativeDir ?? "." },
{ header: "Models", getValue: (row) => row.models.join(", ") || "unknown" },
{ header: "Input", getValue: (row) => formatTokenValue(row.usage.inputTokens, false), align: "right" },
{ header: "Cached", getValue: (row) => formatTokenValue(row.usage.cachedInputTokens, false), align: "right" },
{ header: "Output", getValue: (row) => formatTokenValue(row.usage.outputTokens, false), align: "right" },
{ header: "Reason", getValue: (row) => formatTokenValue(row.usage.reasoningOutputTokens, false), align: "right" },
{ header: "Total", getValue: (row) => formatTokenValue(row.usage.totalTokens, false), align: "right" },
{ header: "Cost", getValue: (row) => formatUsd(row.costUsd, row.hasUnpricedUsage), align: "right" },
{ header: "Last Activity", getValue: (row) => formatTimestamp(row.lastTimestamp, report.timezone, report.locale) }
];
}
function buildBreakdownLines(report, row) {
if (!report.breakdown || row.breakdown.length === 0) {
return [];
}
return row.breakdown.map((item) => {
const cost = formatUsd(item.costUsd, !item.hasPricing);
const output = item.usage.outputTokens + item.usage.reasoningOutputTokens;
return ` - ${item.model}: input=${formatTokenValue(item.usage.inputTokens, report.compact)}` + ` cached=${formatTokenValue(item.usage.cachedInputTokens, report.compact)}` + ` output=${formatTokenValue(output, report.compact)}` + ` cost=${cost}`;
});
}
function renderReport(report) {
const lines = buildSummary(report);
const columns = report.reportType === "session" ? buildSessionColumns(report) : buildDailyOrMonthlyColumns(report);
if (report.rows.length === 0) {
lines.push("");
lines.push("No usage records matched the selected filters.");
return lines.join(`
`);
}
lines.push("");
lines.push(renderTable(report.rows, columns));
lines.push("");
lines.push(`TOTAL input=${formatTokenValue(report.totals.usage.inputTokens, report.compact)}` + ` cached=${formatTokenValue(report.totals.usage.cachedInputTokens, report.compact)}` + ` output=${formatTokenValue(report.totals.usage.outputTokens + report.totals.usage.reasoningOutputTokens, report.compact)}` + ` cost=${formatUsd(report.totals.costUsd, report.totals.hasUnpricedUsage)}`);
if (report.breakdown) {
for (const row of report.rows) {
const breakdownLines = buildBreakdownLines(report, row);
if (breakdownLines.length === 0) {
continue;
}
lines.push("");
lines.push(`${row.label} breakdown`);
lines.push(...breakdownLines);
}
}
return lines.join(`
`);
}
// src/cdxusage/parser.ts
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
function isObject(value) {
return typeof value === "object" && value !== null;
}
function toNumber(value) {
return typeof value === "number" && Number.isFinite(value) ? value : 0;
}
function parseTokenTotals(value) {
if (!isObject(value)) {
return null;
}
const inputTokens = toNumber(value.input_tokens);
const cachedInputTokens = toNumber(value.cached_input_tokens);
const outputTokens = toNumber(value.output_tokens);
const reasoningOutputTokens = toNumber(value.reasoning_output_tokens);
const providedTotalTokens = toNumber(value.total_tokens);
return {
inputTokens,
cachedInputTokens,
outputTokens,
reasoningOutputTokens,
totalTokens: providedTotalTokens || inputTokens + outputTokens
};
}
function subtractTotals(current, previous) {
const hasReset = current.totalTokens < previous.totalTokens || current.inputTokens < previous.inputTokens || current.cachedInputTokens < previous.cachedInputTokens || current.outputTokens < previous.outputTokens || current.reasoningOutputTokens < previous.reasoningOutputTokens;
if (hasReset) {
return current;
}
return {
inputTokens: current.inputTokens - previous.inputTokens,
cachedInputTokens: current.cachedInputTokens - previous.cachedInputTokens,
outputTokens: current.outputTokens - previous.outputTokens,
reasoningOutputTokens: current.reasoningOutputTokens - previous.reasoningOutputTokens,
totalTokens: current.totalTokens - previous.totalTokens
};
}
function hasUsage(totals) {
return totals.inputTokens > 0 || totals.cachedInputTokens > 0 || totals.outputTokens > 0 || totals.reasoningOutputTokens > 0 || totals.totalTokens > 0;
}
function parseRateLimits(timestamp, value) {
if (!isObject(value)) {
return null;
}
const primary = isObject(value.primary) ? value.primary : null;
const secondary = isObject(value.secondary) ? value.secondary : null;
const credits = isObject(value.credits) ? value.credits : null;
return {
timestamp,
planType: typeof value.plan_type === "string" ? value.plan_type : null,
primaryUsedPercent: primary ? toNumber(primary.used_percent) : null,
primaryWindowMinutes: primary ? toNumber(primary.window_minutes) : null,
primaryResetsAt: primary ? toNumber(primary.resets_at) : null,
secondaryUsedPercent: secondary ? toNumber(secondary.used_percent) : null,
secondaryWindowMinutes: secondary ? toNumber(secondary.window_minutes) : null,
secondaryResetsAt: secondary ? toNumber(secondary.resets_at) : null,
creditsBalance: credits ? toNumber(credits.balance) : null,
creditsHasUnlimited: credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null
};
}
function collectJsonlFiles(dir) {
if (!fs.existsSync(dir)) {
return [];
}
const files = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
for (const childPath of collectJsonlFiles(fullPath)) {
files.push(childPath);
}
continue;
}
if (entry.isFile() && fullPath.endsWith(".jsonl")) {
files.push(fullPath);
}
}
return files.sort();
}
function parseSessionId(filePath) {
return path.basename(filePath, ".jsonl");
}
function parseSessionShortId(sessionId) {
return sessionId.slice(-8);
}
function parseSessionRelativeDir(filePath, sessionRoot) {
const relativeDir = path.relative(sessionRoot, path.dirname(filePath));
return relativeDir === "" ? "." : relativeDir;
}
function parseUsageFile(filePath, sessionRoot) {
const raw = fs.readFileSync(filePath, "utf8");
const lines = raw.split(`
`);
const sessionId = parseSessionId(filePath);
const sessionShortId = parseSessionShortId(sessionId);
const sessionRelativeDir = parseSessionRelativeDir(filePath, sessionRoot);
const records = [];
let currentModel = null;
let previousTotals = null;
let latestRateLimits = null;
for (const line of lines) {
if (line.trim() === "") {
continue;
}
let parsed;
try {
parsed = JSON.parse(line);
} catch {
continue;
}
if (!isObject(parsed)) {
continue;
}
if (parsed.type === "turn_context" && isObject(parsed.payload) && typeof parsed.payload.model === "string") {
currentModel = parsed.payload.model;
continue;
}
if (parsed.type !== "event_msg" || !isObject(parsed.payload) || parsed.payload.type !== "token_count") {
continue;
}
const timestamp = typeof parsed.timestamp === "string" ? parsed.timestamp : null;
if (!timestamp) {
continue;
}
const usageInfo = isObject(parsed.payload.info) ? parseTokenTotals(parsed.payload.info.total_token_usage) : null;
const rateLimits = parseRateLimits(timestamp, parsed.payload.rate_limits);
if (rateLimits && (!latestRateLimits || timestamp > latestRateLimits.timestamp)) {
latestRateLimits = rateLimits;
}
if (!usageInfo) {
continue;
}
const delta = previousTotals ? subtractTotals(usageInfo, previousTotals) : usageInfo;
previousTotals = usageInfo;
if (!hasUsage(delta)) {
continue;
}
records.push({
timestamp,
model: currentModel,
sessionId,
sessionShortId,
sessionRelativeDir,
sessionFile: filePath,
totals: delta
});
}
return {
records,
latestRateLimits
};
}
function resolveCodexHome(explicitCodexHome) {
if (explicitCodexHome) {
return explicitCodexHome;
}
return process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
}
function parseCodexUsage(explicitCodexHome) {
const codexHome = resolveCodexHome(explicitCodexHome);
const sessionRoot = path.join(codexHome, "sessions");
const sessionFiles = collectJsonlFiles(sessionRoot);
const records = [];
let latestRateLimits = null;
for (const filePath of sessionFiles) {
const parsed = parseUsageFile(filePath, sessionRoot);
records.push(...parsed.records);
if (parsed.latestRateLimits && (!latestRateLimits || parsed.latestRateLimits.timestamp > latestRateLimits.timestamp)) {
latestRateLimits = parsed.latestRateLimits;
}
}
return {
codexHome,
sessionRoot,
sessionFiles: sessionFiles.length,
records,
latestRateLimits
};
}
// src/cdxusage/pricing.ts
var PRICING_TABLE = [
{
canonicalModel: "gpt-5.4",
inputUsdPerMillion: 2.5,
cachedInputUsdPerMillion: 0.25,
outputUsdPerMillion: 15
},
{
canonicalModel: "gpt-5.3-codex",
inputUsdPerMillion: 1.5,
cachedInputUsdPerMillion: 0.15,
outputUsdPerMillion: 10
},
{
canonicalModel: "gpt-5.1-codex",
inputUsdPerMillion: 1.25,
cachedInputUsdPerMillion: 0.125,
outputUsdPerMillion: 10
},
{
canonicalModel: "gpt-5.1-codex-mini",
inputUsdPerMillion: 0.25,
cachedInputUsdPerMillion: 0.025,
outputUsdPerMillion: 2
}
];
var MODEL_ALIASES = {
"gpt-5-codex": "gpt-5.1-codex",
"gpt-5-codex-mini": "gpt-5.1-codex-mini",
"gpt-5.4-codex": "gpt-5.4"
};
function normalizeModelKey(model) {
return model.trim().toLowerCase();
}
function getPricingRate(model) {
if (!model) {
return null;
}
const normalized = normalizeModelKey(model);
const aliased = MODEL_ALIASES[normalized] ?? normalized;
const exact = PRICING_TABLE.find((rate) => rate.canonicalModel === aliased);
if (exact) {
return exact;
}
if (aliased.includes("gpt-5.4")) {
return PRICING_TABLE[0] ?? null;
}
if (aliased.includes("gpt-5.3-codex")) {
return PRICING_TABLE[1] ?? null;
}
if (aliased.includes("mini")) {
return PRICING_TABLE[3] ?? null;
}
if (aliased.includes("codex")) {
return PRICING_TABLE[2] ?? null;
}
return null;
}
function calculateApiEquivalentCostUsd(usage, model) {
const rate = getPricingRate(model);
if (!rate) {
return null;
}
const nonCachedInputTokens = Math.max(0, usage.inputTokens - usage.cachedInputTokens);
const outputTokens = usage.outputTokens + usage.reasoningOutputTokens;
const inputCost = nonCachedInputTokens / 1e6 * rate.inputUsdPerMillion;
const cachedCost = usage.cachedInputTokens / 1e6 * rate.cachedInputUsdPerMillion;
const outputCost = outputTokens / 1e6 * rate.outputUsdPerMillion;
return inputCost + cachedCost + outputCost;
}
// src/cdxusage/report.ts
function createEmptyTotals() {
return {
inputTokens: 0,
cachedInputTokens: 0,
outputTokens: 0,
reasoningOutputTokens: 0,
totalTokens: 0
};
}
function addTotals(target, source) {
target.inputTokens += source.inputTokens;
target.cachedInputTokens += source.cachedInputTokens;
target.outputTokens += source.outputTokens;
target.reasoningOutputTokens += source.reasoningOutputTokens;
target.totalTokens += source.totalTokens;
}
function createAccumulator(key, label) {
return {
key,
label,
firstTimestamp: "",
lastTimestamp: "",
sessionIds: new Set,
models: new Set,
usage: createEmptyTotals(),
costUsd: 0,
hasUnpricedUsage: false,
breakdown: new Map
};
}
function getRecordDayKey(record, timezone) {
return getDateKey(record.timestamp, timezone);
}
function applyRecord(accumulator, record) {
accumulator.sessionIds.add(record.sessionId);
const modelLabel = record.model ?? "unknown";
accumulator.models.add(modelLabel);
if (accumulator.firstTimestamp === "" || record.timestamp < accumulator.firstTimestamp) {
accumulator.firstTimestamp = record.timestamp;
}
if (accumulator.lastTimestamp === "" || record.timestamp > accumulator.lastTimestamp) {
accumulator.lastTimestamp = record.timestamp;
}
addTotals(accumulator.usage, record.totals);
const costUsd = calculateApiEquivalentCostUsd(record.totals, record.model);
if (costUsd === null) {
accumulator.hasUnpricedUsage = true;
} else {
accumulator.costUsd += costUsd;
}
const existingBreakdown = accumulator.breakdown.get(modelLabel);
if (existingBreakdown) {
addTotals(existingBreakdown.usage, record.totals);
if (costUsd === null) {
existingBreakdown.hasPricing = false;
} else {
existingBreakdown.costUsd += costUsd;
}
return;
}
const rate = getPricingRate(record.model);
accumulator.breakdown.set(modelLabel, {
model: modelLabel,
canonicalModel: rate?.canonicalModel ?? null,
usage: { ...record.totals },
costUsd: costUsd ?? 0,
hasPricing: costUsd !== null
});
}
function finalizeAccumulator(accumulator) {
const breakdown = Array.from(accumulator.breakdown.values()).sort((left, right) => {
if (right.costUsd !== left.costUsd) {
return right.costUsd - left.costUsd;
}
return left.model.localeCompare(right.model);
});
return {
key: accumulator.key,
label: accumulator.label,
firstTimestamp: accumulator.firstTimestamp,
lastTimestamp: accumulator.lastTimestamp,
sessionCount: accumulator.sessionIds.size,
models: Array.from(accumulator.models).sort(),
usage: accumulator.usage,
costUsd: accumulator.costUsd,
hasUnpricedUsage: accumulator.hasUnpricedUsage || breakdown.some((item) => !item.hasPricing),
breakdown,
sessionId: accumulator.sessionId,
sessionShortId: accumulator.sessionShortId,
sessionRelativeDir: accumulator.sessionRelativeDir
};
}
function sortRows(rows, order) {
return rows.sort((left, right) => {
const comparison = left.key.localeCompare(right.key);
return order === "asc" ? comparison : -comparison;
});
}
function filterRecords(records, timezone, since, until, sessionFilter) {
return records.filter((record) => {
if (sessionFilter && !record.sessionId.includes(sessionFilter) && !record.sessionShortId.includes(sessionFilter)) {
return false;
}
const dayKey = getRecordDayKey(record, timezone);
if (since && dayKey < since) {
return false;
}
if (until && dayKey > until) {
return false;
}
return true;
});
}
function normalizeFilters(options) {
let since = options.since ? normalizeDayInput(options.since) ?? undefined : undefined;
let until = options.until ? normalizeDayInput(options.until) ?? undefined : undefined;
let period = options.period;
if (options.reportType === "daily" && options.period) {
const normalizedDay = normalizeDayInput(options.period);
if (!normalizedDay) {
throw new Error(`Invalid day period: ${options.period}`);
}
since = normalizedDay;
until = normalizedDay;
period = normalizedDay;
}
if (options.reportType === "monthly" && options.period) {
const normalizedMonth = normalizeMonthInput(options.period);
if (!normalizedMonth) {
throw new Error(`Invalid month period: ${options.period}`);
}
const bounds = getMonthBounds(normalizedMonth);
since = bounds.since;
until = bounds.until;
period = normalizedMonth;
}
return {
since,
until,
period
};
}
function buildDailyRows(records, timezone) {
const byDay = new Map;
for (const record of records) {
const key = getDateKey(record.timestamp, timezone);
const accumulator = byDay.get(key) ?? createAccumulator(key, key);
applyRecord(accumulator, record);
byDay.set(key, accumulator);
}
return Array.from(byDay.values()).map(finalizeAccumulator);
}
function buildMonthlyRows(records, timezone) {
const byMonth = new Map;
for (const record of records) {
const key = getMonthKey(record.timestamp, timezone);
const accumulator = byMonth.get(key) ?? createAccumulator(key, key);
applyRecord(accumulator, record);
byMonth.set(key, accumulator);
}
return Array.from(byMonth.values()).map(finalizeAccumulator);
}
function buildSessionRows(records, timezone, locale) {
const bySession = new Map;
for (const record of records) {
const key = record.sessionId;
const accumulator = bySession.get(key) ?? createAccumulator(key, record.sessionShortId);
accumulator.sessionId = record.sessionId;
accumulator.sessionShortId = record.sessionShortId;
accumulator.sessionRelativeDir = record.sessionRelativeDir;
applyRecord(accumulator, record);
accumulator.label = `${record.sessionShortId} (${formatTimestamp(record.timestamp, timezone, locale)})`;
bySession.set(key, accumulator);
}
return Array.from(bySession.values()).map(finalizeAccumulator);
}
function buildTotalsRow(rows) {
const accumulator = createAccumulator("TOTAL", "TOTAL");
for (const row of rows) {
accumulator.sessionIds.add(row.key);
row.models.forEach((model) => accumulator.models.add(model));
if (accumulator.firstTimestamp === "" || row.firstTimestamp && row.firstTimestamp < accumulator.firstTimestamp) {
accumulator.firstTimestamp = row.firstTimestamp;
}
if (accumulator.lastTimestamp === "" || row.lastTimestamp > accumulator.lastTimestamp) {
accumulator.lastTimestamp = row.lastTimestamp;
}
addTotals(accumulator.usage, row.usage);
accumulator.costUsd += row.costUsd;
accumulator.hasUnpricedUsage = accumulator.hasUnpricedUsage || row.hasUnpricedUsage;
for (const item of row.breakdown) {
const existing = accumulator.breakdown.get(item.model);
if (existing) {
addTotals(existing.usage, item.usage);
existing.costUsd += item.costUsd;
existing.hasPricing = existing.hasPricing && item.hasPricing;
continue;
}
accumulator.breakdown.set(item.model, {
model: item.model,
canonicalModel: item.canonicalModel,
usage: { ...item.usage },
costUsd: item.costUsd,
hasPricing: item.hasPricing
});
}
}
accumulator.sessionIds = new Set(rows.map((row) => row.sessionId ?? row.key));
return finalizeAccumulator(accumulator);
}
function buildReport(data, options) {
const timezone = options.timezone;
const normalized = normalizeFilters(options);
const filteredRecords = filterRecords(data.records, timezone, normalized.since, normalized.until, options.sessionFilter);
const reportRows = (() => {
switch (options.reportType) {
case "monthly":
return buildMonthlyRows(filteredRecords, timezone);
case "session":
return buildSessionRows(filteredRecords, timezone, options.locale);
case "daily":
default:
return buildDailyRows(filteredRecords, timezone);
}
})();
const order = options.order ?? "desc";
const rows = sortRows(reportRows, order);
const totals = buildTotalsRow(rows);
return {
reportType: options.reportType,
timezone,
locale: options.locale,
since: normalized.since,
until: normalized.until,
period: normalized.period,
sessionFilter: options.sessionFilter,
compact: options.compact ?? false,
breakdown: options.breakdown ?? false,
rows,
totals,
latestRateLimits: data.latestRateLimits,
codexHome: data.codexHome,
sessionFiles: data.sessionFiles
};
}
// src/utils/terminal.ts
import * as fs2 from "fs";
import * as path2 from "path";
var __dirname = "/Users/junyu/coding/ccdash/src/utils";
var PACKAGE_VERSION = "0.1.8";
function getPackageVersion() {
if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
return PACKAGE_VERSION;
}
const possiblePaths = [
path2.join(__dirname, "..", "..", "package.json"),
path2.join(__dirname, "..", "package.json")
];
for (const packageJsonPath of possiblePaths) {
try {
if (fs2.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
return packageJson.version ?? "";
}
} catch {}
}
return "";
}
// src/cdxusage.ts
function printHelp() {
console.log(`cdxusage v${getPackageVersion() || "dev"}
Usage:
cdxusage [daily] [YYYY-MM-DD] [options]
cdxusage monthly [YYYY-MM] [options]
cdxusage session [session-id] [options]
Options:
--since <YYYY-MM-DD> Filter by start day (inclusive)
--until <YYYY-MM-DD> Filter by end day (inclusive)
--timezone <IANA name> Group usage in the given timezone
--locale <locale> Locale used for human-readable timestamps
--json Emit JSON instead of a table
--compact Use compact number formatting
--breakdown Include per-model breakdowns
--order <asc|desc> Sort report rows (default: desc)
--codex-home <path> Override CODEX_HOME for log discovery
--help Show this message
--version Show the CLI version
`);
}
function requireValue(args, index, flag) {
const value = args[index + 1];
if (!value || value.startsWith("-")) {
throw new Error(`${flag} requires a value`);
}
return value;
}
function parseArgs(argv) {
const args = [...argv];
let reportType = "daily";
let period;
let sessionFilter;
let timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
let locale;
let json = false;
let compact = false;
let breakdown = false;
let order = "desc";
let since;
let until;
let codexHome;
const first = args[0];
if (first === "--help" || first === "-h") {
printHelp();
process.exit(0);
}
if (first === "--version" || first === "-v") {
console.log(getPackageVersion() || "dev");
process.exit(0);
}
if (first === "daily" || first === "monthly" || first === "session") {
reportType = first;
args.shift();
}
if (args[0] && !args[0].startsWith("-")) {
if (reportType === "session") {
sessionFilter = args[0];
} else {
period = args[0];
}
args.shift();
}
for (let index = 0;index < args.length; index++) {
const arg = args[index];
switch (arg) {
case "--help":
case "-h":
printHelp();
process.exit(0);
break;
case "--version":
case "-v":
console.log(getPackageVersion() || "dev");
process.exit(0);
break;
case "--since":
since = requireValue(args, index, "--since");
index++;
break;
case "--until":
until = requireValue(args, index, "--until");
index++;
break;
case "--timezone":
timezone = requireValue(args, index, "--timezone");
index++;
break;
case "--locale":
locale = requireValue(args, index, "--locale");
index++;
break;
case "--json":
json = true;
break;
case "--compact":
compact = true;
break;
case "--breakdown":
breakdown = true;
break;
case "--order": {
const value = requireValue(args, index, "--order");
if (value !== "asc" && value !== "desc") {
throw new Error(`--order must be asc or desc, received: ${value}`);
}
order = value;
index++;
break;
}
case "--codex-home":
codexHome = requireValue(args, index, "--codex-home");
index++;
break;
case "--id":
sessionFilter = requireValue(args, index, "--id");
index++;
break;
case "--mode":
case "--offline":
case "-O":
if (arg === "--mode") {
index++;
}
break;
default:
throw new Error(`Unknown argument: ${arg}`);
}
}
return {
reportType,
period,
sessionFilter,
since,
until,
timezone,
locale,
json,
compact,
breakdown,
order,
codexHome
};
}
function main() {
try {
const args = parseArgs(process.argv.slice(2));
const parsedUsage = parseCodexUsage(args.codexHome);
const report = buildReport(parsedUsage, {
reportType: args.reportType,
period: args.period,
sessionFilter: args.sessionFilter,
since: args.since,
until: args.until,
timezone: args.timezone,
locale: args.locale,
compact: args.compact,
breakdown: args.breakdown,
order: args.order
});
if (args.json) {
console.log(JSON.stringify(report, null, 2));
return;
}
console.log(renderReport(report));
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
console.error(`cdxusage: ${message}`);
process.exit(1);
}
}
main();

Sorry, the diff of this file is too big to display