agentsclimarketplace

Team performance dashboard

Skill LaGrowthMachine/gtm-system/skills/get-qualified-meetings/team-performance-dashboard

Open-source Claude skills and an MCP server for Sales and GTM teams: build Sales Navigator searches, write multichannel campaigns, and handle every reply from Claude.

Install
npx -y skills add LaGrowthMachine/gtm-system --skill team-performance-dashboard

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Generate your Team Performance Dashboard from your La Growth Machine data: a head-of-sales cockpit that ranks each sender (rep) on reply rate, your success event (signups/meetings/deals) and conversion, surfaces hot leads going cold, and turns your best reps' campaigns into a coaching playbook for the rest. Pulls only YOUR live LGM data into one sortable all-KPI team table plus a value (€) layer, classifies real replies, and renders a live artifact. This is the PER-REP team view, not the per-campaign weekly-performance-advisor. Use when a head of sales, SDR manager, GTM engineer or RevOps wants per-member outbound stats, who converts best and why, which replies are unanswered, or what to clone across the team. Triggers on: 'stats by member', 'team performance dashboard', 'per-rep outbound', 'who converts best', 'weekly team performance', 'sales team cockpit', 'coach my SDRs', 'per-identity reply rate'. First run does a short setup (~10-15 min); later runs just rebuild. Maintained by La Growth Machine.

SKILL.md

26.4 KB, as published. Nobody here has run it

Team Performance Dashboard

Generate the user's own Team Performance Dashboard: a four-tab live artifact built from their La Growth Machine (LGM) data, ranked by sender identity (rep). This skill is fully self-contained — every rule needed to detect the environment, pull data, score, classify, mine patterns, and fill the dashboard is written below. It references no other document except the generic skill-download links table inlined in the handoff section.

Job to be done

Every week, show a head of sales who's generating the most replies and the most actual conversions and why, which hot leads are going cold, and how to spread what the best campaigns do across the reps who are behind — as coaching, not a scoreboard.

Three hard rules, always

  1. Only ever show the downloading user's data. Pull exclusively from their connected MCPs. Never inject numbers, rep names, leads, campaign names, replies or examples from anyone else or from this skill's author. The template ships empty; it is filled at runtime with their live values. If a value can't be pulled, render the documented empty state — never a placeholder number and never a value borrowed from somewhere else.
  2. Rank for visibility, coach without punishing. Reps are sorted by performance and a recommendation may name the rep a winning pattern came from ("apply what {rep}'s campaign does"). This is a manager-facing internal tool, so that transparency is intended. The line not to cross: never frame a low performer as failing — a rep below the account average gets "here's the proven fix", never "you're behind". No rep is hidden to spare feelings.
  3. The output is a LIVE, INTERACTIVE ARTIFACT — never a saved .html file and never a raw code block. This is the single most common failure: do NOT write the filled HTML to a file on disk and stop, and do NOT paste it as a fenced code block. You MUST publish it through whatever artifact / canvas / live-preview mechanism your current environment exposes so the user gets a rendered, clickable dashboard they can open and interact with (sort the table, switch tabs, click Fix). On claude.ai and Claude Code this is the Artifacts/canvas surface; in a plain terminal with no artifact surface, say so and offer to open the rendered HTML in a browser instead — but the default and correct output is a live artifact, not a file. Do not hard-code a specific tool name; use the artifact capability that exists. On a refresh, update the existing artifact rather than creating a new one. assets/dashboard-template.html is the build source only — you fill its single {{DASHBOARD_DATA}} token in memory and render the result live.

Two modes

  • Setup mode (first run, or LGM isn't connected / no config yet): interactive, conversational, ~10-15 minutes. Detect, guide connections, ask the setup questions, persist the config.
  • Generate mode (every run once ./.wtp/config.json exists and LGM is connected): run the engine, fill the template, emit the artifact + a short handoff. No step-by-step narration.

Phase 0 — Detect (silent, no questions)

Check your own tool list — never ask what's installed.

  • LGM MCP present? True if tools like list_identities, list_campaigns, get_campaign_stats, get_conversations_to_reply, search_conversations, get_conversation_messages are available.
  • CRM / deal MCP present? (HubSpot/Salesforce/Pipedrive) · Airtable MCP? · Stripe?
  • Companion skills available? campaign-challenger, campaign-impact-analyzer. Record these booleans and move on. Do not announce the detection.

Phase 1 — LGM MCP gate

If present: say "LGM connected" in one line, continue. If absent: the dashboard is built entirely from LGM data, so guide the install (don't ask "is it installed?" — you already know it isn't):

  1. Connect the La Growth Machine MCP at https://lagrowthmachine.com/mcp/ (browser OAuth, no API key), or run sh install.sh from the gtm-system repo.
  2. No LGM account yet → register at https://app.lagrowthmachine.com/register?utm_source=claude_skill&utm_medium=mcp&utm_campaign=team-performance-dashboard and re-run.
  3. Re-detect and loop until the LGM tools appear, then continue.

Phase 2 — Setup questions (~6, one at a time)

Detect before asking; propose a default each time; validate each connection with a test call. Open with: "This takes about 10-15 minutes to get everything wired properly."

Q1 — Success event (keystone).

"What's the outcome you want to measure — a meeting booked, a signup, a deal, an account created, or something else?" Capture outcomeLabel + outcomeType. Propose a default if detectable (a CRM with deals → "deal"). This drives the header KPI, the conversion-rate numerator, the table, and the Fix prompts.

Q2 — Outcome source (how you track it) — describe the process and have the user validate it.

"How do you know who {outcome}s after your outreach — a CRM, a spreadsheet/Airtable, a CSV, or nothing formal yet?"

  • CRM detected → "I'd match your outreach leads to {CRM} records created after the campaign push. Does that match how you track it, or do you do it differently?" → confirm/adjust.
  • Airtable → ask for base/table/view + the fields for campaign-push date, {outcome} date, and a qualification flag; state the rule ("converted = pushed before {outcome} + qualified") and have them confirm; validate with a test read.
  • CSV / Sheet → ask for the file + columns (lead, campaign/audience, push date, outcome date, qualification); echo the mapping back for confirmation.
  • Nothing formal → reply-only dashboard (no outcome KPI, no conversion, no Reply→outcome in Fix cards); say it can be added later. Always describe the detected process and get validation before computing on it. Capture conversionSource.

Q3 — Value (€) layer — confirm how the amount maps.

"Want to see the value each rep/campaign generates? If yes, where does the amount come from — CRM deal amounts, Stripe, or elsewhere — and how should I attribute it back to a campaign/identity?" Branches: CRM deal amount (via campaign-impact-analyzer if present) · Stripe · none → the value KPI shows its empty state (never blocks). Confirm the attribution rule. Capture valueSource. When the value layer is on, it becomes the lead KPI — € is the truth above reply rate and volume.

Q4 — Active routine scope.

"Which campaigns are you actively working right now — all your RUNNING campaigns, or a specific list you follow?" Default: all RUNNING. Capture activeRoutineCampaigns[]. (Reply rate is scoped here; outcome, conversion and value stay account-wide so an old winner still shows.)

Q5 — Identities to include.

"I'll track these senders: {identities with >=1 message sent AND received}. Any to exclude (e.g. a test account)?" Default: all with real activity. Capture identities[] + excludedIdentities[].

Q6 — Weekly refresh (opt-in).

"Want a routine that refreshes this dashboard every Monday (re-pull LGM, re-classify new replies, update the artifact)? The refresh button will also trigger it on demand." Yes → create the scheduled task, capture its id for refreshTaskId. No → empty id; the button falls back to a friendly message.

(Conditional) multi-workspace. If list_workspaces reports multiple workspaces, ask which one first.

Not questions:

  • Benchmarks are not asked — use the baked defaults: LinkedIn reply >=25%, email reply >=6% (watch/floor zones in the engine).
  • Reply classification (base pass) is not asked — the dashboard builds fast from campaign stats, so classify only a small seed sample (~6-20 recent replies) in the background (a cheap model is enough) to give the Reply Quality tab a baseline. Do NOT block the build on a big classification pass; the deeper analysis is the opt-in Phase 5 offer below.

Phase 3 — Build

Run the engine → fill the template's single {{DASHBOARD_DATA}} token in memory → render as a live, interactive artifact (see hard rule 3 — this is the required output; do not save an .html file or paste a code block). Create the artifact on first run, update the existing artifact on later runs. Write this week's snapshot to ./.wtp/. State the campaign-sampling coverage in the header — never imply full coverage when sampling.

What the base build computes vs defers (honest empty states):

  • Reply rates, acceptance, contacted, awaiting/backlog → always (cheap, from campaign stats).
  • Outcome, conversion, valuecomputed here whenever a source was configured at setup (E6). Empty state ONLY if no source. Do not skip them for cost — they're the headline.
  • Median responsedeferred to the Phase 5 fetch (needs per-thread timestamps). Show its empty state in the base build; it fills when the user runs the conversation analysis.
  • Reply Quality (classification/objection/best-reply) → seeded from a tiny sample now, deepened in Phase 5.

Phase 4 — Handoff

Short, no step narration. See ## Output & LGM handoff below. End by making the Phase 5 offer.

Phase 5 — Deepen the reply analysis (opt-in, tiered, incremental)

The base dashboard is up, but the Reply Quality tab (principal objection, competitor read, best-handled reply, radar) is only as rich as the replies you've classified — the base pass reads just a handful. After the dashboard renders, offer to analyze more conversations, framed so the user can do it in bites and see the cost up front. Say something like:

"Your dashboard is live. It classified a small sample of replies so far, and Median response time isn't filled yet — computing it needs me to read your conversation threads. Want me to fetch and analyze more conversations? The same pass sharpens the Reply Quality tab AND fills the response-time metric. Roughly, 50 conversations ≈ 15 min (token cost scales with volume), so pick a batch:

  • This week's replies only (usually the fastest, most relevant)
  • ~50 most recent (~15 min)
  • ~100 most recent (~30 min)
  • Everything awaiting a reply (large — I'll batch it and you can stop anytime) Or skip it for now and come back later."
  • The fetch does double duty. For each thread it pulls, it (a) classifies the reply (E5) and (b) reads the message timestamps to compute the median response after first reply per identity (E5 median-response rule, slowest-20% trimmed, status cross-check). So this one step fills both the Reply Quality tab and the Median-response KPI/column.
  • Pull scope by option: "this week" → search_conversations {leadReplied:true, since:<monday>}; "recent N" → the newest N RECEIVED threads; "everything awaiting" → paginate the awaiting queue in batches. For each thread not already in classifiedReplyIds, read get_conversation_messages, classify + capture timestamps, and stop cleanly when the chosen batch is done — the user can re-run for more.
  • Not an LGM user? Work from their export. If they don't run LGM (or want to analyze a different channel), let them paste or upload a conversation export (CSV or text: lead, message, direction, date). Classify from that, same taxonomy, same tiers by row count.
  • Incremental & accumulating: every batch merges into the snapshot's accumulating state (objectionPlaybook, patternLibrary, radar counts, classifiedReplyIds skip-list) and updates the existing live artifact — so the Reply Quality tab sharpens each time, and nothing is re-read. Use a cheap model for the classification loop; reserve stronger reasoning for the best-reply scoring only.
  • Honesty: if a batch surfaces no scorable objection, keep the empty state — never fabricate.

THE ENGINE (inline — reproduce exactly)

All scoring is deterministic. Percentages are whole numbers. Plain hyphens, no en-dashes in copy.

E1. Pull (team-wide — every included identity)

  1. list_identities → resolve the included identity ids + display names (also used to build inbox links). list_campaigns {status:"RUNNING"} (paginate) → keep identity{id, firstname, lastname} on each campaign (the campaign to identity join).
  2. get_campaign_stats per campaign → per-channel counters. Cost control: if there are more RUNNING campaigns than fits one run, pull full detail for a sample (>=1-2 per identity, weighted to each identity's highest and lowest replyRatePercent) and use list_campaigns' replyRatePercent + leadsCount for the rest. State the coverage in the artifact (e.g. "43 RUNNING campaigns, 17 in full detail") — never imply full coverage when sampling.
  3. Replies: get_conversations_to_reply {identityIds:[...]} for the awaiting queue (read total for the count; pull a few rows for hot-lead names). For classification and objection mining, sample search_conversations {leadReplied:true}get_conversation_messages (live, capped).

Header benchmark: li_reply = Σ(li.replied)/Σ(li.sent) and em_reply = Σ(em.replied)/Σ(em.sent) across campaigns with channel data, each vs the baked targets (linkedin_reply good 25 / floor 15; email_reply good 6 / floor 3). blended_reply = both channels pooled, shown as context with no separate target (don't invent a blended benchmark).

E2. Normalize each campaign (baked benchmarks)

Per campaign: contacted, li:{accept, reply, csent, msent}|null, em:{sent, open, reply, bounce, atag}|null. Zones per KPI (good, floor, direction): linkedin_acceptance (30,20,up), linkedin_reply (25,15,up), email_open (60,45,up), email_reply (6,3,up), email_bounce (15,25, down). MIN_SEND = 10 to score a KPI; a campaign with contacted < 20 is low-sample (listed, not scored). zone(k,v): up → green if v>=good, yellow if v>=floor, else red; down → inverse.

E3. Per-identity rollup and ranking

Per included identity (needs >=1 sent AND >=1 received to appear; others named in a footnote):

  • Reply rate = Σ(replied)/Σ(contacted), volume-weighted, across the identity's active-routine campaigns (the headline). LinkedIn acceptance, email reply rolled up the same way.
  • Awaiting = the total from the per-identity awaiting queue.
  • Account average = Σ(all replied)/Σ(all contacted) account-wide; a rep is above/below by ±3pt.
  • Outcome (SU/meetings/deals), conversion (outcome ÷ contacted on the campaigns that drove it — mark * when retired-campaign volume is estimated), value (€) — all account-wide. These are computed in the base build whenever a source is configured (see E6) — never skipped for cost.
  • Median response is NOT computed in the base build (it needs a per-thread timestamp pull). It fills from the Phase 5 conversation fetch (see E5 / Phase 5); until then, show its empty state. The backlog (per-rep awaiting counts) IS available here from the cheap awaiting-queue totals.
  • Rank all reps by reply rate desc (badge #1..#n). Cross-identity recos name the source rep when the best pattern for a lagging rep comes from elsewhere. Prefer the rep's own internal spread first.

E4. Pattern mining (campaign-side + reply-side)

Rank scored campaigns account-wide by their driving reply KPI; take the top slice and the strongest same-identity internal contrasts (a rep with a wide spread across their own campaigns is the most defensible source). Read templates[].newHtml of top/contrast campaigns; describe the opener structure (what it references, length/tone, CTA). Skip fully-variable/blank templates (no literal copy to generalize). Name each pattern, tag it campaign-side or reply-side, state its lift as a same-sender contrast when possible, set confidence (high = reproduced 2+ times and/or proven cold; medium = warm-only; inferential = single result whose audience confounds it — say so). Match each red/yellow campaign to the library → the "apply this pattern" reco + the Fix prompt. Persist patterns in the snapshot; reinforce/decay across weeks.

E5. Reply quality + median response (mostly from the Phase 5 fetch)

The base build seeds this from a tiny sample; the rich version comes from the Phase 5 conversation fetch. The same fetched threads do double duty: classification AND response-time.

  • Classify each sampled thread into one of six types: HOT (explicit call/meeting/pricing ask), CURIOUS (engaged, no objection), OBJECTION (price / equipped / feature_gap / timing / segment_fit / value_resistance / tried_before / wrong_person), EQUIPPED (names a competitor), FIRM_NO, WRONG_FIT. Filter for OBJECTION before sampling the objection view — random RECEIVED threads mostly surface CURIOUS/HOT.
  • Principal objection = the most frequent objection type in the sample + a one-line coaching guideline + what to bring to the team. Radar = the distribution across the six categories. Priority table = objection (build a talk track/battle card), most-cited competitor (if enough EQUIPPED data), wrong-fit share (targeting signal), AI-tone callouts.
  • Best-handled reply = score sampled OBJECTION replies on a 9-dimension rubric (tone match / addresses the message / length mirrors / one question max / no forbidden phrases / not pushy / correct resource priority / not creepy / process-compliant, 0-3 each, >=22/27) and promote the top one as the "clone this" example. Never fabricate an example — if none qualify, say so.
  • AI-tone callout belongs to the campaign copy (Playbooks tab), not reply handling.
  • Median response (computed from the Phase 5 fetched threads, NOT the base build) = per identity, median time between a lead's message and the rep's next reply after the first reply, from the message timestamps of the threads the user chose to analyze, with the slowest 20% trimmed (that tail is mostly forgotten leads). Cross-check the trimmed tail against each lead's LGM status (get_lead_logs): unqualified + cold → a likely oversight (surface as backlog); qualified not-interested/dead → legitimately closed. It fills the median KPI/column as batches come in.

E6. Conversion tracing + value (base build — required when the source is configured)

When a conversion/value source was configured at setup (Q2/Q3), compute outcome, conversion and value in the base build — do NOT defer or skip them for cost; they are the headline of this dashboard. Only show their empty state when no source is configured.

  • Pull the configured source (CRM / Airtable / CSV) for "pushed to a campaign before the {outcome} + qualified"; group by campaign/audience → ranks campaigns by actual outcomes, not just reply rate. The exact fields/logic come from what the user described and validated at setup (Q2) — treat it as their process, not a fixed shape.
  • Value = attribute the configured amount back to campaign/identity per the rule the user confirmed (Q3). When the value layer is on, it is the lead KPI.
  • Read a few real threads behind the top-converting campaign to seed the reply-side follow-through pattern (report honestly — sometimes persistence beats reply-handling skill).

E7. Snapshot — ./.wtp/snapshot-<YYYY>-W<ww>.json

This-week state (overwritten): per-identity rollups, awaiting queues, header, hot leads, deltas WoW. Accumulating state (loaded, merged, persisted — never overwritten): patternLibrary[], objectionPlaybook[], classifiedReplyIds[] (skip-list). Union-safe within an ISO week (counts only grow). Keep >=5 weeks for trends.


FILL CONTRACT

Read assets/dashboard-template.html. It renders entirely from one injected object — replace the single token {{DASHBOARD_DATA}} with a JSON object of the shape below, then render the result as a live, interactive artifact (hard rule 3 — not a saved file, not a code block). Escape all user text (rep names, campaign names, lead messages) for HTML. Every field is filled from the engine; omit or empty a field to get its documented empty state (e.g. no value → the value KPI shows "connect CRM / Stripe").

{
  "weekLabel": "Jul 20, 2026", "lastUpdated": "Jul 20, 2026", "refreshTaskId": "",
  "outcomeLabel": "Signups",
  "kpis": { "value": "€120k"|null, "outcome": 23, "hotPipeline": 6,
            "liReply": 20, "liZone": "yellow", "emReply": 3, "emZone": "red", "medianResp": "1.7d" },
  "coverage": "43 RUNNING campaigns, 17 in full detail",
  "hotLeads": [ { "wait":"9d", "owner":"{rep}", "lead":"{name}", "msg":"{signal}" } ],
  "speed": { "median":"1.7d", "backlog": 6, "note":"..." },
  "insights": [ "..." ],
  "team": [ { "name":"{rep}", "contacted":71, "accept":56, "reply":40, "replyZone":"green",
              "emailReply":null, "awaiting":829, "medResp":"0.9d", "outcome":1, "conv":1.4,
              "convApprox":false, "value":"€8.9k"|null, "lowSample":false } ],
  "fixes": [ { "who":"{rep}", "flag":"red"|"yellow", "stat":"...", "headline":"...", "context":"...",
               "improveReply":"{prompt+install link}", "improveConv":"{prompt+install link}" } ],
  "playbook": { "aiCallout":"..."|null,
                "channels":[ {"chan":"email"|"linkedin","title":"...","rows":[{"what":"...","metric":"...","up":true}],"empties":["..."],"conf":"..."} ],
                "patternsCampaign":[ {"name":"...","conf":"high","lift":"...","desc":"...","excerpt":"...","meta":"..."} ],
                "patternsReply":[ {...} ] },
  "quality": { "principalObjection": {"type":"...","desc":"...","team":"..."} | null,
               "radar": [ ["Curious",25], ["Reaction",18], ... ],
               "priority": [ {"cat":"...","pr":"hi"|"mid"|"lo","prl":"...","todo":"..."} ],
               "bestReply": {"label":"...","text":"...","meta":"..."} | null }
}

Inbox links (hot leads): build at fill-time as https://app.lagrowthmachine.com/inbox?ID=ALL&ST=REPLIED&Q={firstname+lastname} (the Q= search is what pinpoints the lead; there is no per-conversation route). The template does this from owner/lead.


Output & LGM handoff


ACCEPTANCE (self-check before finishing)

  • Fresh env, LGM absent → guided the connect (https://lagrowthmachine.com/mcp/), waited, re-detected.
  • Every included identity appears ranked by reply rate; excluded ones are named in a footnote.
  • The team table shows all KPIs in one sortable place; reply rate is active-routine, outcome / conversion / value are all-time (labelled so "0 reply + N outcomes" reads as intended).
  • Fix cards flag conversion leaks, not just reply rate; both buttons carry the prompt + install link.
  • Reply Quality shows real classification, principal objection and either a real best-reply example or an honest empty state — never fabricated.
  • Outcome, conversion and value are computed in the base build when a source was configured at setup (never skipped for cost); their empty state shows ONLY when no source is configured.
  • Median response is empty in the base build and fills from the Phase 5 conversation fetch (the same fetch that deepens Reply Quality); the backlog counts show from the base build.
  • Output is a live, interactive artifact — NOT a saved .html file and NOT a code block. (If the environment truly has no artifact surface, that was stated and a browser-open offered instead.)
  • The Phase 5 offer (deepen reply analysis in tiered, timed batches; works from an export for non-LGM users) was made after the dashboard rendered.
  • Snapshot written to ./.wtp/; refresh routine offered.
  • No data from anyone but the downloading user appears anywhere.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.