plugins/adobe-cja/skills/cja-kpi-pulse/SKILL.md
Produces a compact KPI digest showing how key metrics changed over a period and what's driving the movement. Use this skill when someone asks for a performance summary, a weekly recap, a morning briefing, a KPI update, or any variation of "how did we do this week/month." Also trigger for requests like "give me a performance overview," "what moved in the last 7 days," "pull our KPI report," or "summarize our metrics."
npx skillsauth add adobe/skills cja-kpi-pulseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Produce a compact KPI digest in under 2 minutes. The goal is a crisp answer to "how did we do?" — not a deep-dive, not a data dump. Each KPI gets a scorecard showing current value, period-over-period change, trend direction, and the top dimension breakdown that explains any movement.
describeCja(DATAVIEW_CONTEXT_GUIDE) — understand the data view contextlistComponentUsage — find the most-used metrics (the org's real KPIs)findMetrics — resolve metric IDs from user-specified namesfindCalculatedMetrics — include custom KPIs if presentrunReport — pull metric values for current and prior periodssearchDimensionItems — top dimension breakdown for moversfindDataViews to list available data views.setDefaultSessionDataViewId with the chosen ID.describeCja("DATAVIEW_CONTEXT_GUIDE") to load data view context.
Record the data view's first-day-of-week as WEEK_START_DOW and timezone
as TIMEZONE. If the context guide does not return a week-start value,
default to Monday (ISO 8601). You will use both in Phase 1.1.If the user did not specify a period, ask one question:
"What time window would you like? Options: last 7 days, last 30 days, this week vs last week, this month vs last month, or a custom range."
Default to this week vs last week if no answer is given.
Map the answer to two date ranges:
Calendar rule (mandatory):
Use WEEK_START_DOW from Phase 0 to define what "week" means. The current
period (Period A) and the comparison period (Period B) MUST use the same
first-day-of-week — i.e., both periods' startDate fall on the same
day-of-week, both are exactly equal length, and the comparison period ends
immediately before the current period starts. Never mix conventions
(e.g., a Mon–Sun current with a Sun–Sat prior) within the same pulse run.
Pick the boundary once, then derive both periods from it. For custom date
ranges, compute Period B as the equal-length window ending immediately
before Period A starts.
Sanity check before calling runReport: confirm periodA.startDate and
periodB.startDate are the same day-of-week and that
periodA.startDate - periodB.endDate == 1 day. If not, recompute.
If the user named specific metrics, resolve them with findMetrics or
findCalculatedMetrics. Otherwise, discover the top 5–8 KPIs automatically:
listComponentUsage(componentType: "metric")
listComponentUsage(componentType: "calculatedMetric")
Note: listComponentUsage may return an empty list for data views with no
usage history. If it returns empty, fall back to:
findMetrics(searchQuery: "sessions visits revenue orders")
findMetrics(searchQuery: "page views cart conversion")
Pick the most business-relevant metrics from the results (sessions, orders, revenue, product views, cart views, people — in that priority order).
Deduplicate: if a built-in metric and a calculated metric measure the same thing, keep only the calculated metric (it's more intentional).
Final list: 5–8 metrics. More than 8 KPIs in a pulse report is noise.
Run a single runReport call per period with all KPI metrics included.
Use one call for Period A and one for Period B to minimize round-trips.
Use a summary dimension (e.g., variables/daterangeday) and limit: 1 to
get aggregate totals from summaryData.totals in the response.
runReport(
dimensionIds: "variables/daterangeday",
metricIds: "metrics/visits,metrics/visitors,metrics/orders_1_1,metrics/productListItems.priceTotal,metrics/cart_views",
startDate: "<periodA start>T00:00:00",
endDate: "<periodA end>T23:59:59",
page: 0,
limit: 1
)
runReport(
dimensionIds: "variables/daterangeday",
metricIds: "metrics/visits,metrics/visitors,metrics/orders_1_1,metrics/productListItems.priceTotal,metrics/cart_views",
startDate: "<periodB start>T00:00:00",
endDate: "<periodB end>T23:59:59",
page: 0,
limit: 1
)
Read aggregate totals from summaryData.totals (not row data), which
gives you the full-period sum for each metric in the order they were listed.
Capture for each metric:
valueA (current period)valueB (comparison period)delta = valueA − valueBpctChange = (delta / valueB) × 100, rounded to 1 decimalFor each KPI, assign a trend indicator:
Assign a signal color:
For the 1–2 metrics with the largest absolute % change, find what's driving the movement. Run a dimension breakdown for the current period:
runReport(
dimensionIds: "variables/marketing_channel",
metricIds: "<moving metric id>",
startDate: "<periodA start>T00:00:00",
endDate: "<periodA end>T23:59:59",
page: 0,
limit: 5
)
Note: Use variables/marketing_channel (not variables/marketingchannel) —
verify the exact dimension ID with findDimensions(searchQuery: "marketing channel")
if unsure.
Compare dimension values between Period A and Period B to identify the top contributor to the change. This becomes the "What drove it" entry in the report.
Generate the KPI Pulse HTML report INLINE — do not use a Python script.
Build the HTML string directly from the collected data and output it as a
code block the user can save, or write it to /tmp/cja_kpi_pulse_report_<YYYY-MM-DD_HHMMSS>.html
using a one-line bash command.
Two runs of this skill on the same data view + period must render identically (modulo the generation timestamp). The rules below pin the formatting choices that the AI would otherwise drift on.
8,160, 77,584, 1,250,000). Do NOT use SI
suffixes like K or M, even for large values. Executives want exact
numbers, not abbreviations.−23.55% displays as
−23.6%, never −23.5%. Compute on full-precision values; round only at
display time.pp. Example: +0.40 pp.$ prefix with thousands separators and no decimals for
values ≥ $100 ($1,240,000); cents only when value < $100 ($45.20).A KPI tile must reflect what the data view actually returned. The AI must not silently substitute a different metric or hide a tile to make the report look cleaner.
kpi-value = Data unavailable, pill class flat, pill text
⚠ N/A, and prior text = Both periods returned no data — validate instrumentation. The tile stays in the grid; do not omit it.kpi-value, pill class flat, pill text ⚠ N/A, and
prior text = Prior {period_noun}: no data.Read template.html and use it verbatim. Do not improvise the
HTML structure or CSS — only fill in the {PLACEHOLDER} tokens ({ORG_NAME},
{PERIOD_LABEL}, {COMPARISON_LABEL}, {DATA_VIEW}, {GENERATED_DATE},
{METRIC_NAME}, {FORMATTED_VALUE_A}, {FORMATTED_VALUE_B}, {PCT_CHANGE},
{VALUE_A}, {VALUE_B}, {DELTA}, {ARROW}) and repeat the KPI tile / detail
row / mover row blocks once per data item. Preserve the .up | .down | .flat
and .green | .red | .yellow | .grey modifier classes per the trend rules in
Phase 3.
After generating the HTML:
/tmp/cja_kpi_pulse_report_<YYYY-MM-DD_HHMMSS>.htmlopen /tmp/cja_kpi_pulse_report_<YYYY-MM-DD_HHMMSS>.htmlKPI Pulse — This Week vs Last Week
↑ Revenue: $1.24M (+8.2%) — Paid Search drove most of the gain
↓ Conversion Rate: 2.1% (−0.4pp) — Drop in mobile checkout
→ Sessions: 540K (+1.1%) — Flat week-over-week
↑ Orders: 11,340 (+6.7%) — Product page improvements appear to be working
↓ Bounce Rate: 43.2% (+2.1pp) — Worth monitoring next week
The text summary gives immediate value even without opening the HTML file.
"Give me a quick pulse on our key metrics for this week."
findDataViews. User selects their main data view. Call setDefaultSessionDataViewId.runReport for current week vs. prior week for all four metrics.runReport returns no data for Period B (comparison is too far in the
past or data view lacks history), show "N/A" for the delta and flag it with
a grey badge.tools
Use the run-workflow MCP to discover, compose, execute, publish, and save Adobe Firefly workflows. TRIGGER when: user asks what actions are available, what the MCP can do, how to process images/video/3D via workflow, wants to build/run/save/publish a workflow, OR pastes any workflow/batch/execution ID. BARE ID (UUID/workflowId/batchId) = INSPECT ONLY — call inspect_run, NEVER run_workflow_submit. ALWAYS call list_actions first for capability/discovery questions. DO NOT TRIGGER for direct Firefly API calls without MCP (use firefly-api-specs).
tools
Run predefined featured workflows via run-workflow MCP. TRIGGER when user names a featured workflow (retargeting, banners at scale, localization, packaging, banner advertising, etc.) or asks to run a known marketing/production workflow. Requires run-workflow MCP. ALWAYS call get_featured_workflow before compose_workflow. DO NOT TRIGGER for custom one-off workflows with no named template — use run-workflow skill.
tools
Migrate an Adobe Commerce App Builder project from the Integration Starter Kit or Checkout Starter Kit to the new App Management approach. Run from the root of the App Builder project to be migrated. Pass --auto to skip confirmation prompts (suitable for CI or batch use) — auto mode prints a summary of all Q&A questions answered with their defaults. Pass --doc-scan-only to scan README.md and env.dist for outdated content without modifying any files. Use when the user wants to migrate an App Builder project from the Integration Starter Kit or Checkout Starter Kit to the App Management approach, or mentions upgrading their Adobe Commerce extension architecture.
development
Add or modify webhook interceptors in an Adobe Commerce app. Use when the user wants to intercept Commerce operations to validate input, append data, or modify behavior — before or after execution. Requires a base app initialized with commerce-app-init.