Sales Control Panel
A sales dashboard that reads your email, calendar, calls and pipeline, then brings your top priorities to your attention.
- ✓Reads your email, calendar, calls and pipeline, then builds today's plan.
- ✓Tracks every promise you have made, so nothing slips.
- ✓About 15 minutes to set up, then it refreshes itself every morning.
If you don't have the Claude desktop app, you'll need it first, so download it here.
(You'll also need a Claude Pro Plan, around $20)
Install Cowork
Download the Claude desktop app and sign in on a paid plan. It has to be the desktop app, not the browser, because that is the version that can read your files and connect to your tools.
Open Cowork and give it a folder
Cowork is where Claude actually does work on your computer, not just chat. Get in and point it at one folder:
- Open the desktop app and switch to Cowork, the middle of the three modes at the top.
- Click New Task, then Work in a Project, then Choose a folder.
- Make a fresh, empty folder (I call mine Claude Playground).
Claude only ever sees that one folder, nothing else on your computer, and it uses it to save your panel and your data. So it stays private and tidy.
Run the panel
Paste the Master Prompt below into that task and send it. It then asks you two quick questions: what you sell, and where your deal data lives.
Tip: not ready to connect your own data? Pick the demo data and watch it build first, so you can see it working before plugging anything in.
Connect your tools (when ready)
Go to Customize > Connect your apps. Then for each tool:
- Type the tool into the search bar, for example Gmail.
- Click it, a browser window opens, you log in, done.
- Repeat for your other tools. The ones that matter most are your email and your pipeline or CRM.
Quick tip: you can run it without your own data first, just to see how it feels. Then come back to these steps to connect your apps and make it properly yours.
Where it lives
Once it is built, open Live Artifacts in the left sidebar. That is your panel. It refreshes automatically at 5am. If your computer is off at 5am, it runs the moment you next open the desktop app, so it is always ready when you sit down.
You are building a **Sales Control Panel** for me as a live Cowork artifact. This is a daily operating system for sales, not a dashboard. Every panel must answer "what do I do next?". Passive dashboards drown the day; action surfaces run it.
## What you are building
A single-page HTML artifact, persisted in Cowork, that opens each morning and shows me what to do today. Populated from connected data sources and an Excel deals file. The artifact is the **view**. `deals.xlsx` is the **state**. Gmail, the connected call-transcription tool, Calendar, and any prospect connector are **inbound channels**. Never invent critical data. If a field is missing, ask or fall back to a stated default. Never fabricate.
Five zones, in this order:
1. **KPI strip** — Action needed today, Stale deals, Pipeline this month (weighted), Meetings today.
2. **Today** — Today's plan + Today's meetings (with click-to-expand prep).
3. **Commitments** — read-only ledger of promises I have made.
4. **Pipeline** — Stale deals + Pipeline forecast with stage drill-down.
5. **Footer** — data source attribution and refresh meta.
## Before you start — connector check
Match connectors by **tool-name suffix**, not full name (Cowork MCP tool names carry a session-random server id). Look for these suffixes:
- `__search_threads` → Gmail
- `__list_events` → Google Calendar
- `__get_meeting_transcript` or `__list_meetings` → call transcription (Fathom, Granola, Fellow, Grain, Fireflies, Circleback all qualify; use the friendly name in the UI based on which one resolves)
- Apollo prospect enrichment is optional
When you write these into the artifact's `window.cowork.callMcpTool(...)` calls, paste the **full session-resolved tool name**, not the suffix. Use ToolSearch to discover them.
Note which connectors resolved, but **do not stop here**. The connector check only matters for live commitment extraction, and the demo seed (Appendix A) provides its own commitments, so a connector-less member must still be able to see the panel. Defer any stop until after Q2: if I answer Q2 with **demo data**, always continue regardless of connectors. Only if I answer Q2 with **real/live data** (drop a file, workspace path, or CRM export) **and** both Gmail and a call-transcription tool are missing, stop at that point and tell me the commitments tracker has nothing to work with. If only one is missing, continue.
## Before Q1 — fit check
This tool fits B2B-style deals: named prospects, multi-touch sales cycles with stages (Discovery, Demo, Proposal, Negotiation), commitments owed to specific people, weighted pipeline. It does not fit e-commerce, marketplaces (Amazon, Shopify, Etsy), ad performance, or transactional flows without a named buyer.
If my offer reads like one of those, stop and flag the mismatch in one short paragraph. Then offer three picks: (a) build the panel for my B2B side instead (wholesale buyers, retailer pitches, licensing deals), (b) seed demo data so I can see what the panel does, (c) this isn't the right tool, stop here. Wait for my answer before moving to Q1.
## Step 1 — Ask two questions, one at a time
Ask each question on its own. Wait for my answer before asking the next. Do not proceed to Step 2 until both are answered. If I cancel or reject either, default to "AI consulting" / use demo data, tell me what you defaulted to in one line, and continue.
**Stale-deal threshold is fixed at 5 days. Don't ask about it.**
1. **What do you sell, in one sentence?** Free text. If I give a category ("AI consulting company"), accept it and continue. Don't loop.
2. **Where is your deals data?** Options: (a) drop an Excel/CSV file, (b) workspace file path, (c) export from a CRM (HubSpot, Pipedrive, Salesforce, Attio — propose a nightly sync into `deals.xlsx`), (d) use demo data (seed `deals.xlsx` from Appendix A so I can see the panel populated; swap in real data later).
Do not ask about colour, layout, or anything visual. Those are fixed below.
**File-upload shortcut:** if I drop a file into uploads at any point during setup, treat that as the answer to Q2(a) and proceed straight to schema mapping.
## Step 2 — Clean and map the deals file
Three rules before column-matching:
1. **Strip summary and total rows.** User files commonly carry `SUMMARY`, `Total unweighted`, `Weighted closing ≤ 31 May` rows. Filter the Deals sheet to rows where `deal_id` matches `^[DR]-\d+$` (or whatever id convention I use). Drop everything else.
2. **Drop stored derived columns** (`weighted_value`, `days_since_touch`, anything that should be computed from primary fields). Compute on read, never store.
3. **Then** fuzzy-match my columns to the canonical schema. Ambiguous = ask one targeted question. Missing = follow the rules.
### Canonical schema (Deals sheet)
| Field | Type | Required? | If missing |
|---|---|---|---|
| deal_id | string | yes | Generate `D-001`, `D-002`… |
| prospect_name | string | yes | Stop and ask |
| prospect_title | string | no | Blank |
| prospect_email | string | no | Blank (no source link possible) |
| company_name | string | yes | Stop and ask |
| company_size | int | no | Blank |
| location | string | no | Blank |
| stage | enum | yes | Stop and ask |
| probability | percent | no | Stage defaults: Discovery 20%, Demo 40%, Proposal 60%, Negotiation 80%, Active Retainer 100%, Closed Won 100%, Closed Lost 0% |
| deal_type | enum | no | Default "One-off" |
| deal_value | number | yes | Stop and ask |
| monthly_value | number | no | Blank unless deal_type = Retainer |
| expected_close | date | no | Blank (excluded from "this month" KPI) |
| last_touch | date | yes | If the column exists but rows are empty, fill those rows with `expected_close - 30 days` and tell me the count. If the column is absent entirely, stop and ask. |
| source_channel | string | no | Blank |
| whale | bool | no | Auto-flag the top 10% by deal_value among open one-off deals, tie-inclusive: rank open one-off deals by deal_value, take the value at the 10% cutoff rank (round, minimum 1), and flag every deal whose deal_value is greater than or equal to that cutoff value. Boundary ties are all flagged. |
| notes | string | no | Blank |
### Cohort folder
Each demo or cohort lives in its own subfolder under the workspace so multiple cohorts don't tangle (I might run a demo and a real-data version side-by-side; people who paste this prompt will be running their own demo).
Pick a short cohort label: one word, lowercase, hyphens fine. Derive it from the buyer cohort in my Q1 offer sentence: "AI consulting for UK accountancy firms" becomes `accountancy-firms`; "marketing services for SaaS founders" becomes `saas-founders`. If the offer is too generic to derive a useful cohort (just "software" or "consulting"), default to `default`. If I explicitly name a cohort in my message ("call this run X"), use that instead. If the filename I dropped already hints at the cohort (e.g. `deals-marketing-agency.xlsx`), prefer that hint.
Tell me in one line which label you picked.
Create `cohort-<label>/` under the workspace root if it doesn't exist. If it already exists with a different `deals.xlsx` inside, append `-v2` (or `-v3`, etc.) and use that. Save the cleaned file as `cohort-<label>/deals.xlsx`. The artifact reads `deals.xlsx` exclusively from this cohort path. Use this same path everywhere a path is needed downstream: the build script, the scheduled refresh task, the CRM sync task.
### Companion sheets (create if absent)
- **Commitments** — commit_id, promise_text, source_quote (≤120 chars), source_type (Email/Call), source_date, owed_to, deal_id, linked_value, due_date, status, created_date.
- **Plan** — task_id, task_text, source_type (Commit/Stale/Prep/Manual/Carry/Retainer), linked_id, status, date_added, date_completed, carry_count.
- **Daily snapshot** — date, tasks_total, tasks_done, tasks_carried, meetings_held, replies_received, commits_added, commits_closed.
## Step 3 — Build the artifact
Single self-contained HTML file. Inline all CSS/JS. Light mode only.
### Build pipeline (mandatory)
1. Build the JSON data object in Python from cleaned `deals.xlsx`. Normalise NaN → null, dates → ISO strings, integer-valued floats → int.
2. Embed it into the HTML template using a `const DATA = __DATA_JSON__;` placeholder + Python `str.replace`. Do not build via string concatenation — apostrophes, em dashes, and unicode in deal notes will break things.
3. Run `node --check` against the embedded `<script>` block before pushing. The `£`-in-identifier bug, smart-quote escapes, and accidental `</script>` strings all blank the panel silently; node catches them in under a second.
4. Choose the artifact id by data source so a demo run never overwrites a real panel: if I answered Q2 with **demo data**, use id `sales-control-panel-demo`; if I answered with **real/live data**, use id `sales-control-panel`. Then call `list_artifacts`: if the chosen id already exists, use `update_artifact`, otherwise `create_artifact`. Use this same chosen id everywhere downstream (the scheduled refresh task's `Artifact id` line in Step 5 must match it).
### Visual design tokens (do not deviate)
```
--bg:#f6f5ef --bg-soft:#f1efe7 --card:#ffffff --border:#e8e5dc
--ink:#1a1a1a --ink-muted:#6a6a6a --ink-soft:#8a8a8a
--accent:#1f5f4a --accent-soft:#ecf5f1
--warn:#b91c1c --warn-soft:#fef1f1
--warm:#92400e --warm-soft:#fef6ec
--purple:#5b21b6 --purple-soft:#f3eefc
--blue:#1e40af --blue-soft:#eef2fb
shadow:0 1px 2px rgba(15,23,42,0.04)
```
Font: `-apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif`. No animation > 300ms. Tabular numerals on any number column.
### Money format — compact everywhere
`£131k`, `£28.8k`, `£1.3m`. Never `£131,000`. Retainers show monthly value as `£6k/mo` when space is tight. Apply to KPIs, forecast banner, stage rows, stale values, meeting values, commit values, drill-downs.
```js
const fmtGBPCompact = n => {
if (n == null) return '-';
const abs = Math.abs(n);
if (abs >= 1e6) { const m = n/1e6; return '£' + (m%1===0?m.toFixed(0):m.toFixed(1)) + 'm'; }
if (abs >= 1e3) { const k = n/1e3; return '£' + (k%1===0?k.toFixed(0):k.toFixed(1)) + 'k'; }
return '£' + Math.round(n).toLocaleString('en-GB');
};
```
### Responsive
KPI strip stays 4-across at all widths down to 480px, then 2×2 below. Never let it collapse to a single column. Today and Pipeline rows can stack at ≤900px.
```css
.kpi-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
@media (max-width:900px){.row.today,.row.pipeline{grid-template-columns:1fr}}
@media (max-width:480px){.kpi-strip{grid-template-columns:repeat(2,1fr)}}
```
### KPI card alignment
`display:flex;flex-direction:column;min-height:104px`. Label has `min-height:28px;line-height:1.25` so single-line and wrapped labels reserve the same vertical space. Push the sub-line with `margin-top:auto`. Keeps the big numbers at the same y-coordinate across all four.
### Zone 1 — KPI strip
- **Action needed today** — count of (commits due today + overdue). Number red if > 0. Sub: `X overdue · Y due today`.
- **Stale deals** — count where `today - last_touch >= 5` (note `>=`, not `>`). Sub: `Over 5-day cadence · X whales`.
- **Pipeline this month** — `SUM(deal_value × probability)` where `expected_close ≤ end of current month` AND stage not in (Closed Won, Closed Lost, Active Retainer). Sub: `Weighted, close by [last day of month]`.
- **Meetings today** — count from Calendar (or mocks). Sub: `Next at HH:MM` or `No events today`.
### Zone 2 — Today (split 1.5fr / 1fr)
**Left: Today's plan.** Header: title, sub "Auto-seeded from commitments, stale deals and meeting prep", right meta chip `X / Y`. Below: `X of Y done`. Then a dashed-border quick-capture input. Then a `Show N completed` toggle (collapsed by default). Then the list.
Seed order (priority): Open commits with `due_date ≤ today` (badge Commit/red) → top 2-3 stale deals by `days_cold × deal_value` (Stale/warm) → open commits owed to today's meeting attendees (Prep/blue) → retainer renewals within 7 days (Retainer/purple) → yesterday's open carries (Carry/warm, increment carry_count) → manual quick-capture (Manual/accent).
Total 6-10 tasks. Pad with a prospecting task if under 4. Scroll within `max-height:480px` if over 10. Completed tasks strike through and grey out. Manual tasks get a hover X; auto-seeded tasks are not deletable.
**Right: Today's meetings.** Header: title, sub "Live from Google Calendar" (or fallback), right meta chip `N today`.
Row: time (50px bold tabular) · prospect name + firm · context line · value in accent green · stage badge · chevron. Click to expand inline prep. Only one expanded at a time.
Inline prep: soft-background, green "Meeting prep" pill, two-column grid.
**Left column:**
- **AI talking points** — exactly 2 bullets. Each is short (10–15 words), action-led (verb-first: "Lead with…", "Confirm…", "Reference…"), and specific (quotes or paraphrases the prospect's actual language from notes/transcripts/emails). Preserve casing from source fields. At build time these are seeded from `deal.notes` + the top open commit; on expansion the artifact MAY call `window.cowork.askClaude` with the deal, summary, recent emails and open commits to regenerate sharper ones.
- **Last call summary** — italic muted text from whichever call tool is connected. Clickable, with `Open in [Tool name] →`. If none, `No previous calls.`
**Right column:**
- **Recent emails** — exactly 2 (1 received, 1 sent). Direction, subject, snippet, time, `Open in Gmail →`. URL: `https://mail.google.com/mail/u/0/#inbox/<thread_id>`.
- **Open commitments** — list where `owed_to = prospect_name` and `status = Open`. Promise + due-date badge. Empty state: `No open commitments yet.`
### Zone 3 — Commitments tracker (full width)
Read-only table. Sub: "Promises extracted from your sent email and [call tool name] transcripts · read-only · click a row to open source".
```css
.commit-table{table-layout:fixed}
.commit-table td:nth-child(1){width:auto} /* Promise */
.commit-table td:nth-child(2){width:22%} /* Owed to */
.commit-table td:nth-child(3){width:18%} /* Deal */
.commit-table td:nth-child(4){width:90px;text-align:right} /* Value */
.commit-table td:nth-child(5){width:100px} /* Due */
```
Due badges: `min-width:58px;text-align:center;white-space:nowrap` (otherwise `2d late` balloons into an oval).
Source dot: blue for email, purple for call. Click row to open source (Gmail thread URL or call tool URL). Legend strip below.
### Zone 4 — Pipeline (split equal)
**Left: Stale deals.** Each row: warm-soft tile (`#fef6ec`) if days_cold ≤ 7, warn-soft (`#fef1f1`) if ≥ 8 — big number + tiny "days" label. Body: status dot + prospect name + optional WHALE tag (red-soft, **left side**, after the name). Sub: `Stage · last touch [date]`. Right: tabular bold value. Whale tag never glues to the value.
**Right: Pipeline forecast.** Green-accent banner: `Weighted, expected close ≤ [last day of month]` label, large value, sub `From £X total weighted across N open deals`. Then four stage rows: dot colours `#c7d2fe → #93c5fd → #60a5fa → #2563eb` (Discovery → Negotiation). Click stage to expand a drill-down (every deal: name, company, days-since-touch, value). Bottom of drill-down: `Open [stage] stage in Excel →` button (toast: "Filter Stage column in deals.xlsx").
### Header
Brand mark (40px navy `#0f172a` square, white SVG). Title "Sales Control Panel". Sub `[Friday, 15 May 2026 format] · Good morning, [first name]`. First name comes from the env `user` block; if absent, drop the suffix.
Right side: chip row showing all five services (Gmail, Calendar, call tool, prospect enrichment, Excel). Connected = green `#22c55e` dot + full-opacity text; disconnected = grey `#cbd5e1` dot + muted text. Below: `Auto-refreshed at HH:MM · Yesterday` (Yesterday is a clickable link → toast with snapshot stats).
### Footer
`Data sources: [connected services only] · Built in Cowork · Refreshes daily at [refresh time]`.
The refresh time string is rewritten by the daily refresh task using the actual scheduler response, not hard-coded.
### Badges (uniform)
`font-size:10px;padding:2px 8px;border-radius:100px;font-weight:600`. No badge style heavier than the others.
- commit: warn-soft / warn
- stale, carry: warm-soft / warm
- prep: blue-soft / blue
- retainer: purple-soft / purple
- manual: accent-soft / accent
- whale: warn-soft / warn, after prospect name
- demo: `font-size:9px`, bg-soft / ink-soft, uppercase, on any mocked content
### Mocks — bounded set
The artifact must render populated even when connectors return empty. Mocks are scoped to: the four meetings in MOCK_MEETINGS, plus the top 3 stale deals (by days × value). All other deals fall back to "No previous calls." / "No recent emails." Do not author mocks for all 22 seed deals.
```js
const MOCK_MEETINGS = [
{time:'10:30', deal_id:'D-001', stage_label:'Discovery', context:'first call today'},
{time:'12:00', deal_id:'D-009', stage_label:'Demo', context:'asked about pricing'},
{time:'15:00', deal_id:'D-005', stage_label:'Discovery', context:'first call today'},
{time:'16:30', deal_id:'D-015', stage_label:'Proposal', context:'decision this week'}
];
```
Stage labels in mocks must be canonical (Discovery, Demo, Proposal, Negotiation, Active Retainer) — never editorial variants like "Demo 2" or "Intro".
Each `window.cowork.callMcpTool(...)` is wrapped in try/catch. Both errors and empty/null responses fall through to mocks. Live data always takes precedence; the `Demo data` badge only renders when mocks fire.
## Step 4 — Quick capture
Plan card includes a dashed-border input. Enter → prepend a new task with `Manual` badge, show toast `Added to today's plan`, X visible on hover. Manual-only. Does not route to commitments.
## Step 5 — Schedule daily refresh
`mcp__scheduled-tasks__create_scheduled_task`. Default cron `0 5 * * *` (local time).
**Read the actual schedule string from the scheduler response** (it applies a small dispatch delay; e.g. "At 05:05 AM, every day"). Use that string when telling me what time it will run, and substitute it into the artifact footer on the next refresh.
Task prompt (substitute `[OFFER SENTENCE]` with the answer to Q1, `[COHORT FOLDER]` with the cohort folder chosen in Step 2, and `[ARTIFACT ID]` with the id chosen in Step 3 build pipeline step 4: `sales-control-panel-demo` for a demo run, `sales-control-panel` for a real-data run):
> You are the daily refresh task for the Sales Control Panel. The user sells: [OFFER SENTENCE]. State of truth: `[COHORT FOLDER]/deals.xlsx`. Artifact id: `[ARTIFACT ID]`.
>
> 1. Load `[COHORT FOLDER]/deals.xlsx`. Stop and notify if missing.
> 2. Pull sent emails (`__search_threads` with `in:sent newer_than:1d`). Identify explicit promises. Match recipient to a prospect.
> 3. If a call-transcription tool is connected, pull new transcripts and extract verbal commitments.
> 4. Append new commitments. Concise paraphrase, verbatim quote ≤120 chars, due_date relative to send date (blank if ambiguous).
> 5. Recompute the Plan sheet (priority order above). Keep 6-10 tasks.
> 6. Append today's row to Daily snapshot.
> 7. Save `[COHORT FOLDER]/deals.xlsx`. Regenerate the artifact HTML by replacing the `const DATA` JSON literal with fresh data; update header date and refresh time. Run `node --check` before pushing. Call `update_artifact`.
>
> Rules: British English. No em dashes as connectors. Never fabricate. Stage default probabilities as above. Whale = top 10% by deal_value among open one-off deals, tie-inclusive (flag every deal at or above the value at the 10% cutoff rank). Money format `£131k` compact, retainers `£6k/mo`. Preserve casing from source fields. Never use non-ASCII characters in JS identifiers. Stale threshold is fixed at 5 days.
>
> End with: `Daily refresh: N commits added, M tasks on today's plan, plan ready.`
## Step 6 — CRM sync (only if I answered Q2(c))
Register a second scheduled task at `30 4 * * *` (runs before the 05:00 refresh). Pulls open deals from my CRM and merges into `[COHORT FOLDER]/deals.xlsx`, preserving `deal_id` and user-added notes. Updates the `whale` flag. Does not touch Commitments, Plan, or Daily snapshot — those belong to the daily refresh.
## Operating rules
1. Never invent critical fields. Missing data = ask, or blank.
2. The artifact reads `deals.xlsx` only. Never queries a CRM directly.
3. Source links degrade silently. Missing Gmail → drop email links. Missing call tool → drop call links.
4. One source of truth per fact. Stale = `today - last_touch`. Pipeline = `deal_value × probability`. Compute on read.
5. British English.
6. No em dashes as connectors in any user-facing copy (artifact, scheduled task summaries, confirmation messages).
7. Plan list 6-10. Pad if under 4; scroll if over 10.
8. Whale = top 10% by deal_value among open one-off deals, tie-inclusive: rank open one-off deals by deal_value, take the value at the 10% cutoff rank (round, minimum 1), flag every deal whose deal_value is at or above that cutoff. Boundary ties are all flagged (so a £45k tie at the cutoff flags both).
9. CRM users: separate sync task. Artifact still only reads `deals.xlsx`.
10. Demo fallback always on. Mocks render only when live calls return empty. `Demo data` tag is mandatory on mocked content.
11. Validate JS before pushing. The `£`-in-identifier bug blanks the panel silently.
12. Stale threshold is fixed at 5 days. Never ask the user to override it.
## Confirmation message
When done, post one short summary. Money values in compact format. Use the scheduler's actual schedule string.
```
Built. Connectors: [list]. Loaded N deals, M commits. Today's numbers: action X, stale Y, pipeline this month £Zk. Daily refresh: [scheduler string, e.g. "At 05:05 AM, every day"]. Tell me what to tweak.
```
Then stop.
---
## Appendix A — First-run demo seed
22 deals across 5 stages, sized for an SMB or mid-market services seller. Today is the run date; compute `last_touch` and `expected_close` as `today - N days` and `today + N days` at write time, not as absolute dates.
Match the buyer cohort to my Q1 offer sentence. If I sell to accountancy firms, seed accountancy firms; if to marketing agencies, seed marketing agencies; if to manufacturers, seed manufacturers. Pick a plausible UK-based mid-market industry from the offer if it's ambiguous. All firm names fictional, locations spread across UK cities, deal sizes proportionate to the cohort (one-off projects £8k-£35k, retainers £4k-£10k/mo by default; scale up if the offer implies enterprise). Tell me in one line which cohort you seeded.
8 Discovery, 6 Demo, 4 Proposal, 2 Negotiation, 2 Active Retainer. Whales auto-flag from top 10%; typically D-016 (£50k), D-019 (£45k), D-020 (£45k). 8 open commitments seeded into Commitments sheet.
Tune `last_touch` offsets so 6-9 deals land stale at the 5-day threshold. Avoid 18-of-20 stale, looks broken not informative.
Before you finish (quick checks)
- ›Claude desktop app installed, on a paid plan.
- ›One folder created and selected.
- ›Master Prompt pasted and run.
- ›Answered the two questions, or picked demo data.
- ›Panel showing under Live Artifacts.