Best use case
Skill: Cost Reporting is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
## Overview
Teams using Skill: Cost Reporting should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/cost-reporting/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How Skill: Cost Reporting Compares
| Feature / Agent | Skill: Cost Reporting | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
## Overview
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Skill: Cost Reporting
## Overview
Pattern for generating meeting cost reports including Chart.js line charts, per-attendee breakdowns, share percentages, and exportable markdown summaries. Used by the Meeting Cost Calculator SPA's history detail and copy-summary flows.
## Chart.js Cost-Over-Time Line Chart
```ts
// src/components/CostChart.tsx
import { Line } from 'react-chartjs-2';
import {
Chart as ChartJS,
LineElement,
PointElement,
LinearScale,
CategoryScale,
Filler,
Tooltip,
} from 'chart.js';
import type { CostPoint } from '../types';
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Filler, Tooltip);
interface Props {
costHistory: CostPoint[];
currency?: string;
}
export function CostChart({ costHistory, currency = 'USD' }: Props) {
const labels = costHistory.map(p => formatElapsed(p.elapsedSeconds));
const data = costHistory.map(p => p.cost);
return (
<Line
data={{
labels,
datasets: [{
data,
borderColor: '#7c3aed',
backgroundColor: 'rgba(124, 58, 237, 0.08)',
fill: true,
tension: 0.3,
pointRadius: 3,
pointBackgroundColor: '#7c3aed',
}],
}}
options={{
responsive: true,
maintainAspectRatio: false,
scales: {
x: { grid: { color: '#e7e5e4' }, ticks: { color: '#a8a29e', font: { size: 11 } } },
y: {
grid: { color: '#e7e5e4' },
ticks: {
color: '#a8a29e',
font: { size: 11 },
callback: (v) =>
new Intl.NumberFormat('en-US', { style: 'currency', currency, maximumFractionDigits: 0 }).format(Number(v)),
},
},
},
plugins: {
tooltip: {
callbacks: {
label: (ctx) =>
new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(ctx.parsed.y),
},
},
legend: { display: false },
},
}}
/>
);
}
```
## Per-Attendee Cost Breakdown
```ts
export interface AttendeeBreakdown {
attendee: Attendee;
cost: number;
sharePercent: number;
}
export function computeBreakdown(
attendees: Attendee[],
durationSeconds: number,
): AttendeeBreakdown[] {
const breakdown = attendees.map(a => ({
attendee: a,
cost: a.hourlyRate / 3600 * durationSeconds,
sharePercent: 0,
}));
const total = breakdown.reduce((sum, b) => sum + b.cost, 0);
return breakdown.map(b => ({
...b,
sharePercent: total > 0 ? Math.round((b.cost / total) * 100) : 0,
}));
}
```
Shares always sum to approximately 100% because they are rounded independently. For display, this is acceptable. No rounding correction is needed.
## Monthly History Grouping
```ts
export function groupByMonth(meetings: Meeting[]): Record<string, Meeting[]> {
const groups: Record<string, Meeting[]> = {};
for (const m of meetings) {
const label = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long' })
.format(new Date(m.endedAt));
(groups[label] ??= []).push(m);
}
return groups;
}
```
Render each group as a section header followed by `MeetingCard` items.
## Monthly Stats Summary
```ts
export interface MonthStats {
count: number;
totalMinutes: number;
totalCost: number;
}
export function monthStats(meetings: Meeting[]): MonthStats {
return {
count: meetings.length,
totalMinutes: Math.round(meetings.reduce((s, m) => s + m.durationSeconds, 0) / 60),
totalCost: meetings.reduce((s, m) => s + m.totalCost, 0),
};
}
```
## Markdown Summary Format
```
## Meeting Cost Summary
Title: {title}
Date: {endedAt formatted as "MMMM D, YYYY"}
Duration: {HH:MM:SS}
Total cost: ${totalCost}
Rate: ${costPerMin}/min (${totalHourlyRate}/hr combined)
### Attendees
| Name | Rate | Cost | Share |
|------|------|------|-------|
| {name} | ${hourlyRate}/hr | ${cost} | {share}% |
---
Generated by MeetingCost
```
## Clipboard Write
```ts
export async function copyToClipboard(text: string): Promise<boolean> {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// Fallback for older browsers / non-secure contexts
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
const ok = document.execCommand('copy');
document.body.removeChild(ta);
return ok;
}
}
```
## History Export (JSON)
```ts
export function exportHistoryJson(meetings: Meeting[]): void {
const blob = new Blob([JSON.stringify(meetings, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement('a'), {
href: url,
download: `meetingcost-history-${new Date().toISOString().slice(0, 10)}.json`,
});
a.click();
URL.revokeObjectURL(url);
}
```
## History Export (CSV)
```ts
export function exportHistoryCsv(meetings: Meeting[]): void {
const rows = [
['id', 'title', 'startedAt', 'endedAt', 'durationSeconds', 'totalCost', 'attendeeCount'],
...meetings.map(m => [
m.id,
`"${m.title.replace(/"/g, '""')}"`,
m.startedAt,
m.endedAt,
String(m.durationSeconds),
m.totalCost.toFixed(2),
String(m.attendees.length),
]),
];
const csv = rows.map(r => r.join(',')).join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement('a'), {
href: url,
download: `meetingcost-history-${new Date().toISOString().slice(0, 10)}.csv`,
});
a.click();
URL.revokeObjectURL(url);
}
```Related Skills
cloud-cost-estimator
No description provided.
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview
Skill: Syntax Highlighting
## Purpose