Generates user-facing usage statistics, activity summaries, and personalized insights dashboards (weekly recaps, year-in-review, Spotify Wrapped-style). Use when user wants to show usage stats, activity insights, or shareable recap screens. Different from analytics-setup which sends data to a backend — this shows insights to the USER on-device.
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill usage-insights
Generate a production usage insights system that records user activity events with SwiftData, computes personalized insights (streaks, most active day, top categories), and displays them in a dashboard with insight cards, period pickers, trend indicators, and optional shareable recap screens.
Use this skill when the user:
Search for existing usage tracking or insights code:
Glob: **/*UsageEvent*.swift, **/*Insight*.swift, **/*Recap*.swift, **/*ActivityLog*.swift
Grep: "UsageEvent" or "InsightCalculator" or "activitySummary" or "SwiftData" and "event"
If existing analytics/tracking found:
InsightCalculator to work with existing modelsCheck for SwiftData usage:
Grep: "import SwiftData" or "@Model" or "ModelContainer"
If SwiftData already in use:
UsageEvent into existing ModelContainerIf no SwiftData:
ModelContainer configurationAsk user via AskUserQuestion:
UsageEvent model and recorder) — recommendedRead templates.md for production Swift code.
Generate these files:
UsageEvent.swift — SwiftData @Model for recording user activity eventsInsightResult.swift — Model for computed insights (title, value, trend, visualization type)InsightCalculator.swift — Pure functions that aggregate events into insightsInsightsDashboardView.swift — Main dashboard with grid of insight cards and period pickerInsightCardView.swift — Individual insight card with icon, value, trend indicator, sparklineBased on configuration:
UsageRecapView.swift — If shareable recap selected (paged summary with share card generation)UsageEventRecorder.swift — If SwiftData data source selected (convenience class for recording events)Check project structure:
Sources/ exists -> Sources/UsageInsights/App/ exists -> App/UsageInsights/UsageInsights/After generation, provide:
UsageInsights/
├── UsageEvent.swift # SwiftData @Model for activity events
├── InsightResult.swift # Computed insight model
├── InsightCalculator.swift # Aggregation engine
├── InsightsDashboardView.swift # Dashboard with period picker
├── InsightCardView.swift # Individual insight card
├── UsageRecapView.swift # Shareable recap (optional)
└── UsageEventRecorder.swift # Event recording helper (optional)
Add ModelContainer (if not already present):
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [UsageEvent.self])
}
}
Record events from anywhere in the app:
struct TaskDetailView: View {
@Environment(\.modelContext) private var modelContext
@State private var recorder: UsageEventRecorder?
var body: some View {
Button("Complete Task") {
completeTask()
recorder?.record(
.taskCompleted,
metadata: ["category": "work", "priority": "high"]
)
}
.onAppear {
recorder = UsageEventRecorder(modelContext: modelContext)
}
}
}
Show the insights dashboard:
NavigationLink("My Insights") {
InsightsDashboardView()
}
Show a weekly recap:
struct WeeklyRecapSheet: View {
@Environment(\.modelContext) private var modelContext
@State private var showRecap = false
var body: some View {
Button("View Weekly Recap") {
showRecap = true
}
.sheet(isPresented: $showRecap) {
UsageRecapView(period: .week)
}
}
}
Record events with duration tracking:
// Start a timed session
let sessionStart = Date()
// ... user does work ...
// Record when session ends
recorder?.record(
.sessionCompleted,
metadata: ["screen": "editor"],
duration: Date().timeIntervalSince(sessionStart)
)
@Test
func calculatesWeeklySummaryCorrectly() async throws {
let calendar = Calendar.current
let now = Date()
let events: [UsageEvent] = (0..<7).flatMap { dayOffset in
let date = calendar.date(byAdding: .day, value: -dayOffset, to: now)!
return (0..<(dayOffset == 2 ? 5 : 2)).map { _ in
UsageEvent(eventType: "taskCompleted", timestamp: date)
}
}
let calculator = InsightCalculator()
let insights = calculator.weeklySummary(from: events, referenceDate: now)
let totalInsight = insights.first { $0.title == "Total Events" }
#expect(totalInsight != nil)
#expect(totalInsight?.value == "19") // 5 + (6 * 2)
}
@Test
func identifiesMostActiveDay() async throws {
let calendar = Calendar.current
let now = Date()
// Create 5 events on Wednesday, 2 on other days
var events: [UsageEvent] = []
for dayOffset in 0..<7 {
let date = calendar.date(byAdding: .day, value: -dayOffset, to: now)!
let count = calendar.component(.weekday, from: date) == 4 ? 5 : 1
for _ in 0..<count {
events.append(UsageEvent(eventType: "action", timestamp: date))
}
}
let calculator = InsightCalculator()
let insights = calculator.weeklySummary(from: events, referenceDate: now)
let mostActive = insights.first { $0.title == "Most Active Day" }
#expect(mostActive?.value == "Wednesday")
}
@Test
func handlesEmptyEventList() async throws {
let calculator = InsightCalculator()
let insights = calculator.weeklySummary(from: [], referenceDate: Date())
#expect(!insights.isEmpty) // Should still return cards with zero values
let totalInsight = insights.first { $0.title == "Total Events" }
#expect(totalInsight?.value == "0")
}
@Test
func recorderBatchesWritesForPerformance() async throws {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: UsageEvent.self, configurations: config)
let context = ModelContext(container)
let recorder = UsageEventRecorder(modelContext: context)
// Record 100 events rapidly
for i in 0..<100 {
recorder.record(.custom("event_\(i)"))
}
// Flush pending writes
recorder.flush()
let descriptor = FetchDescriptor<UsageEvent>()
let count = try context.fetchCount(descriptor)
#expect(count == 100)
}
recorder?.record(.featureUsed, metadata: ["feature": "darkMode"])
let calculator = InsightCalculator()
let events = try modelContext.fetch(FetchDescriptor<UsageEvent>())
let insights = calculator.weeklySummary(from: events, referenceDate: Date())
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
ForEach(insights) { insight in
InsightCardView(insight: insight)
}
}
let renderer = ImageRenderer(content: UsageRecapView(period: .week))
renderer.scale = 2.0
if let image = renderer.uiImage {
// Use with ShareLink or UIActivityViewController
}
analytics-setup is for)#Predicate with date range)UsageEventRecorder)Calendar.current for date math — never assume 7 days = 1 weekcalendar.firstWeekday)calendar.dateInterval(of: .weekOfYear, for: date) for accurate week boundaries.chartYScale(domain:) to prevent axis from starting at a misleading value.accessibilityLabel on chart marksgenerators/share-card — Render recap views as shareable imagesgenerators/analytics-setup — Backend analytics (complements on-device insights)generators/streak-tracker — Streak tracking pairs well with usage insightsgenerators/milestone-celebration — Celebrate milestones surfaced by insightsComprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas
Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the xlsx in my downloads\") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.
Picks random winners from lists, spreadsheets, or Google Sheets for giveaways, raffles, and contests. Ensures fair, unbiased selection with transparency.
Query openFDA API for drugs, devices, adverse events, recalls, regulatory submissions (510k, PMA), substance identification (UNII), for FDA regulatory data analysis and safety research.
MATLAB and GNU Octave numerical computing for matrix operations, data analysis, visualization, and scientific computing. Use when writing MATLAB/Octave scripts for linear algebra, signal processing, image processing, differential equations, optimization, statistics, or creating scientific visualizations. Also use when the user needs help with MATLAB syntax, functions, or wants to convert between MATLAB and Python code. Scripts can be executed with MATLAB or the open-source GNU Octave interpreter.
UMAP dimensionality reduction. Fast nonlinear manifold learning for 2D/3D visualization, clustering preprocessing (HDBSCAN), supervised/parametric UMAP, for high-dimensional data.
Creating interactive data visualisations using d3.js. This skill should be used when creating custom charts, graphs, network diagrams, geographic visualisations, or any complex SVG-based data visualisation that requires fine-grained control over visual elements, transitions, or interactions. Use this for bespoke visualisations beyond standard charting libraries, whether in React, Vue, Svelte, vanilla JavaScript, or any other environment.
Access AlphaFold 200M+ AI-predicted protein structures. Retrieve structures by UniProt ID, download PDB/mmCIF files, analyze confidence metrics (pLDDT, PAE), for drug discovery and structural biology.
Take rshankras/usage-insights from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.