add-report — скилл для Claude Code: что делает и как установить
Руководство по разработке карточек отчётов для ClaudeBar. Помогает рассчитывать дельты расходов и токенов по дням по методологии TDD.
npx skills add tddworks/ClaudeBar --skill add-report -a claude-codeЧто делает add-report
Скилл задаёт регламент добавления аналитических карточек в ClaudeBar. Он рассчитан на визуализацию истории использования (токены или затраты) со сравнением периодов, например недели к прошлой неделе. Архитектура требует вычислять отчёт строго поверх дней, уже полученных через account.usageHistory?.days(in:). Дополнительный код для чтения файлов не создаётся.
Разработка делится на фазы. Сначала оформляется макет в каталоге design-concept/ и согласовывается с пользователем через AskUserQuestion. Затем создаётся структура отчёта в модуле Quotas строго по TDD с именованием тестов по шаблону should <outcome> [when <situation>]. Карточка в интерфейсе App только отображает вычисленные дельты и проценты.
В ClaudeBar запрещены фоновые вычисления: генерация отчёта запускается только при открытии всплывающего окна. Менять UsageSnapshot напрямую или добавлять парсеры в Sources/Infrastructure нельзя — для новых форматов логов пишутся отдельные ридеры в модуле Modules/DataSources.
Какие задачи решает
- добавить карточку сравнения расходов за текущую и прошлую недели
- рассчитать дельту потребления токенов без прямого чтения логов с диска
- согласовать макет аналитического отчёта перед написанием Swift-кода
- написать тесты для структуры отчёта в модуле Quotas по TDD
Примеры запросов агенту
- Добавь в ClaudeBar карточку сравнения расходов за эту и прошлую недели по скиллу add-report
- Спроектируй отчёт по токенам моделей за последние 30 дней для активного аккаунта
- Создай структуру MonthlyReport в модуле Quotas и напиши для неё тесты
Как установить add-report
1Через Skills CLI (Vercel)
npx skills add tddworks/ClaudeBar --skill add-report -a claude-code2Вручную: положить файлы
~/.claude/skills/add-report/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/add-report/
1Через Skills CLI (Vercel)
npx skills add tddworks/ClaudeBar --skill add-report -a codex2Вручную: положить файлы
.agents/skills/add-report/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add tddworks/ClaudeBar --skill add-report -a cursor2Вручную: положить файлы
.cursor/skills/add-report/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install tddworks/ClaudeBar add-report2Вручную: положить файлы
.github/skills/add-report/SKILL.md1Через Skills CLI (Vercel)
npx skills add tddworks/ClaudeBar --skill add-report -a gemini-cli2Командой платформы
gemini skills install https://github.com/tddworks/ClaudeBar --path .claude/skills/add-reportВручную: положить файлы
.devin/skills/add-report/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add tddworks/ClaudeBar --skill add-report -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о add-report
Как установить add-report в Claude Code?
Выполните команду npx skills add tddworks/ClaudeBar --skill add-report -a claude-code. Файл скилла сохранится по пути ~/.claude/skills/add-report/SKILL.md.
Что делает скилл add-report?
Он ведёт агента по шагам добавления отчётов в ClaudeBar: от проектирования структуры данных и макета до TDD-тестов в модуле Quotas и вёрстки карточки.
Почему отчёт не должен читать файлы логов напрямую?
История использования уже собирается провайдером в виде [DailyUsageStat]. Структура отчёта вычисляет показатели исключительно в памяти и не зависит от диска.
Исходный текст
SKILL.md · Apache-2.0
---
name: add-report
description: >
Guide for adding new report cards to ClaudeBar that analyze local data sources and display
metrics with comparison deltas. Use this skill when:
(1) Adding a new report/analytics card (e.g., weekly summary, model breakdown, session stats)
(2) Showing usage history in a new way, or reading another tool's usage logs
(3) Adding comparison cards that show "today vs previous" style deltas
(4) Building any feature that follows the DailyUsage pattern (days → report value → card)
---
# Add Report Card to ClaudeBar
Add report cards that turn a login's usage history into metrics with comparison
deltas, shown in the existing card style, test first.
## When to Use
This skill covers **report-style features** — cards that:
- Aggregate a login's days of usage (cost, tokens, time, sessions)
- Compare periods (today vs yesterday, this week vs last week)
- Read another tool's usage logs (as data in its definition)
- Display results in cards matching the existing UI
## First, which kind of report is it?
Usage history is data now ([daily-usage design](../../../docs/features/usage-history/daily-usage/design.md)).
A report is almost always a new **view over days the login already reads**, not new
reading code. Place it before anything else:
| The person wants… | It is | You write |
|---|---|---|
| a new look at usage this app already reads — this week vs last, cost per model, a 30-day chart | a **report over days**: the page asks `account.usageHistory?.days(in: range)` and a value in `Quotas` turns the days into the report | a `Quotas` value + its tests, a card. **No reading code** |
| the same history for another tool (its own log files) | a **`usageHistory` block** in that provider's definition | JSON (paths, `where`, fields, `prices`), its golden tests — no Swift |
| history from a log format no reader understands yet | a **new reader**, named for the format, in `Modules/DataSources/Sources/Internal/Logs/` | the reader, test-first in `DataSourcesTests`, then the JSON; a row in ENGINE_DESIGN §1 |
| an answer to a different question than "how much did I use?" | a **capability** (CANONICAL §2.1): declared in the definition, a handle on `Account` that is `nil` when not declared | design first; a runner outside the modules is supplied by the `Engine` (TARGET_ARCHITECTURE §10) |
**Never:** a field on `UsageSnapshot` (the kernel is shrinking toward `Usage`,
[CANONICAL_MODEL §6](../../../docs/architecture/CANONICAL_MODEL.md#6--what-is-deliberately-not-in-the-tree)),
an `XxxAnalyzer` / `XxxAnalyzing` protocol, a parser in `Sources/Infrastructure`, or a
vendor-named type in a module.
> **Reference implementation:** `references/daily-usage-pattern.md` — *TODAY'S USAGE*:
> Claude's logs and prices as data, `UsageLog` in DataSources, `account.usageHistory`,
> `DailyUsageReport` in Quotas, `DailyUsageCardView`. The Leaderboard reads 30 days the
> same way (`login.history.days(in:)` → `DailyTokens.summed`).
> **Check the design first** — the docs are the source of truth ([AGENTS.md](../../../AGENTS.md#design-docs-are-the-source-of-truth)):
> USER_JOURNEYS, CANONICAL_MODEL, TARGET_ARCHITECTURE, then the feature's `design.md`.
## How a report over days flows
```text
<id>.json "usageHistory" ──► UsageLog (DataSources) one per login, built by
reader · PriceList · ProviderFactory from the definition
DayAggregator
│
popover opens (never in the background, #204)
│ ▼
account.usageHistory?.days(in: .last(14)) ──► [DailyUsageStat] (closed days from the ledger)
│
▼
WeeklyReport(days:) a rich value in Modules/Quotas: deltas, percentages, formatting
│
▼
WeeklyCardView renders what the report says — never compares or counts
```
## Workflow
```
Phase 0: Place it and design it (get user approval)
↓
Phase 1: The report value + tests (TDD Red→Green)
↓
Phase 2: Only if needed — the definition's usageHistory, a new reader, or a capability
↓
Phase 3: Mockup, card view, reading it on popover open
↓
Phase 4: Verify, screenshots on mock data, docs
```
---
## Phase 0: Place it and design it (MANDATORY)
### Step 1: Define the report
- **What does the person want to know?** In their words (USER_JOURNEYS).
- **Which kind is it?** The table above.
- **Which days?** `DateRange` (`.last(n)`, a week, today vs yesterday).
- **Which logins?** A report is per login — two logins are two reports, never summed.
- **How many cards?** One per metric, or one combined card.
### Step 2: Diagram it
```
Example: this week vs last week, per login
┌────────────────────────────────────────────────────────────────────┐
│ Providers (module) Quotas (module) App │
│ │
│ account.usageHistory ─► WeeklyReport(days:) ─► WeeklyCardView │
│ .days(in: .last(14)) thisWeek / lastWeek × 3 metrics │
│ → [DailyUsageStat] deltas, %, formatted │
│ │
│ No reading code: claude.json's usageHistory already reads the logs│
└────────────────────────────────────────────────────────────────────┘
```
### Step 3: The pieces
| Piece | Module | One job | Test |
|---|---|---|---|
| `WeeklyReport` | `Quotas` | turns days into the comparison the card shows | `Tests/DomainTests/<Feature>/` |
| `WeeklyCardView` | `App` | renders it | AppTests / screenshots |
| *(only if needed)* `usageHistory` block, a reader, a capability | definition / `DataSources` / `Providers` | — | golden tests / `DataSourcesTests` / `ProvidersTests` |
### Step 4: Mockup and approval
A UI change gets a mockup in `design-concept/<feature>/` on mock data first
(AGENTS.md). Present the doc change, the diagram, the pieces and the mockup;
use `AskUserQuestion` and wait for approval.
---
## Phase 1: The report value (TDD)
Name each test `should <outcome> [when <situation>]`, in the person's words, never a method, type or mechanism verb → [Naming tests](../implement-feature/references/tdd-patterns.md#naming-tests).
**Location:** `Modules/Quotas/Sources/{Name}Report.swift` (beside `DailyUsageReport`),
tests in `Tests/DomainTests/{Feature}/{Name}ReportTests.swift` (beside `DailyUsageReportTests`).
It is built **from days** — `[DailyUsageStat]` — so it never reads a file:
```swift
public struct {Name}Report: Sendable, Equatable {
public let current: [DailyUsageStat] // e.g. this week
public let previous: [DailyUsageStat] // e.g. last week
/// The days split at `boundary`, as the page asks for them.
public init(days: [DailyUsageStat], splitAt boundary: Date) { … }
public var cost: Decimal { current.reduce(0) { $0 + $1.totalCost } }
public var costChangePercent: Double? {
let before = previous.reduce(Decimal(0)) { $0 + $1.totalCost }
guard before > 0 else { return nil } // nil when there was nothing before
…
}
public var formattedCostDelta: String { … } // "+$5.00", "-$1.20": the sign always shown
public var costProgress: Double { … } // current / (current + previous), 0…1
}
```
**Rules:**
- `Decimal` for money, `TimeInterval` for durations; `Locale(identifier: "en_US")` for currency.
- Every comparison, and every amount the value owns written one way (`formattedCostDelta`, through `MoneyFormat`), lives in the value — the view only reads them. The card's sentences and labels are the App's words, not the value's ([question 7](../implement-feature/references/domain-shape.md#seven-questions)).
- A change percent is `nil` when the previous period is zero; deltas always carry a sign.
- `isEmpty` means all zero (`&&`, not `||`).
Write the tests first (state and return values, Chicago school), then the value.
---
## Phase 2: Only if the report needs reading it doesn't have
### 2a. Another tool's history → its definition
Add a `usageHistory` block to `<id>.json` — `files`, `format`, `where`, fields,
`prices`, `sessionGap` — the shape is in the [daily-usage design §2](../../../docs/features/usage-history/daily-usage/design.md#2--the-definition-usagehistory-beside-datasources).
Golden tests in `Modules/Providers/Tests/` over fixture logs (see `ClaudeUsageHistoryTests`).
No Swift.
### 2b. A log format no reader knows → one reader in DataSources
A reader named for the **format** (`JSONLinesReader`, never `AcmeLogReader`) in
`Modules/DataSources/Sources/Internal/Logs/`, test-first in
`Modules/DataSources/Tests/Logs/` with neutral fixtures, yielding the one
`LogRecord` every reader yields. Then the JSON uses it. Add its row to
ENGINE_DESIGN §1. Only scan files changed within the range (`LogFileFinder`).
### 2c. A different question → a capability
Design it in the docs first (CANONICAL §2.1, TARGET_ARCHITECTURE): declared in
the definition, its own types in `Modules/Providers`, an `@Observable` handle on
`Account` that is `nil` when not declared — like `usageHistory` and
`guestPasses`. `ProviderFactory.make` builds it from the definition; a runner
that lives outside the modules (a CLI the App runs, the Keychain) is supplied by
the `Engine` the App builds once. The App never passes anything per provider.
---
## Phase 3: The card, read on popover open
### 3a. The card view
**Location:** `Sources/App/UsageHistory/{Name}CardView.swift`. Match the existing
cards; `DailyUsageCardView` is the reference:
```swift
struct {Name}CardView: View {
let metric: {Name}Metric // one case per card
let report: {Name}Report
let delay: Double // cascading entrance
@Environment(\.appTheme) private var theme
var body: some View {
VStack(alignment: .leading, spacing: 6) {
// header: icon + LABEL · the large value · the progress bar · the delta line
}
.padding(12)
.background(/* theme.cardGradient fill + theme.glassBorder stroke */)
}
}
```
**Card styling checklist:** `.padding(12)` · `theme.cardGradient` + `theme.glassBorder` (1pt) ·
`theme.cardCornerRadius` · `theme.fontDesign` on text · `theme.textPrimary/Secondary/Tertiary` ·
`theme.progressTrack` · hover scale 1.015 · progress bar animated with `delay`.
### 3b. Read the days when the popover opens
Usage history is read when the popover opens, never in the background (#204),
and per login — the page asks the selected login, as `MenuContentView` does:
```swift
.task(id: login.id) {
guard let history = login.usageHistory else { return } // nil: this login offers none
let days = await history.days(in: .last(14))
report = {Name}Report(days: days, splitAt: weekStart)
}
```
The view renders `report`; it never compares, counts or decides from the days itself.
### 3c. Nothing to register
`ProviderFactory.make` builds `usageHistory` from the definition, and every
provider is found by `ProviderCatalog.detect()` (TARGET_ARCHITECTURE §10). The
App passes nothing per provider.
---
## Phase 4: Verify
1. `tuist generate`
2. `xcodebuild test -workspace ClaudeBar.xcworkspace -scheme ClaudeBar-Workspace -destination 'platform=macOS,arch=arm64'` — the full run; `tuist test` skips cached targets
3. Run the real popover on mock data (`scripts/demo-screenshots.sh`) — screenshots, never real names, emails or usage
4. Docs in the same change: the feature's `README.md` / `design.md`, one CHANGELOG line (≤300 chars), `python3 scripts/gen-docs.py && python3 scripts/check-docs.py --strict`
---
## Checklist
### Phase 0
- [ ] Placed: report over days · definition `usageHistory` · new reader · capability
- [ ] Design in the docs, diagram, pieces table, mockup in `design-concept/<feature>/`
- [ ] User approved
### Phase 1
- [ ] `{Name}ReportTests` written first (deltas, nil percent, signs, progress, empty)
- [ ] `{Name}Report` in `Modules/Quotas`, built from `[DailyUsageStat]` — green
### Phase 2 (only if needed)
- [ ] `usageHistory` block + golden tests, or a format-named reader in `DataSources` + its ENGINE_DESIGN row, or a declared capability
- [ ] No `UsageSnapshot` field, no analyzer protocol, no vendor-named Swift
### Phase 3
- [ ] `{Name}CardView` matching the card style
- [ ] Days read on popover open, per login; nothing passed in `ClaudeBarApp`
### Phase 4
- [ ] Full `xcodebuild test` green
- [ ] Screenshots on mock data
- [ ] Docs and CHANGELOG line; docs check passes
© tddworks · Apache-2.0 · tddworks/ClaudeBar