microsoft/coworkdashboard-cowork-dashboard-team-dashboard
| Manager-side team rollup for Copilot Cowork ROI. Aggregates the de-identified stats teammates post (via the Cowork Dashboard Member skill) to a shared Teams channel into ONE anonymized HTML dashboard (five tabs), then emails the channel members a summary with the dashboard and a one-page PDF guide attached. First run asks for the Teams channel link and remembers it; each run reads the latest 15 days and keeps the latest post per person. Numbers only — no names or files; a Role breaks out only when 3+ share it. Small homogeneous teams; not org-wide. Use when the user asks to "build the team Cowork Dashboard", "aggregate my team's Cowork stats", "roll up the channel posts", "manager Cowork Dashboard report", "email the team dashboard", or set up / refresh the rollup.
npx skills add https://github.com/microsoft/CoworkDashboard --skill cowork-dashboard-team-dashboard
Aggregates the de-identified Cowork Dashboard posts teammates publish (via the **Copilot ROI
Member** skill, cowork-dashboard-member) to a shared Teams channel, renders a single self-contained,
anonymized HTML dashboard, and emails the channel members a high-level summary with the
dashboard and a one-page interpretation guide (PDF) attached.
This skill only reads what the Member skill posts. It never harvests anyone's OneDrive and
never sees names, files, or prompts. If a label (business process, skill, Role) doesn't match the
Member bundle's copy, aggregation drifts — the taxonomy files here are byte-for-byte mirrors of
the Member skill's (see *Cross-skill contract*).
contributors sharing one channel.
cowork-roi-report (rich personal web app).cowork-dashboard-member.cowork-roi-report-aggregated.different skills entirely.
say the window was empty and offer to widen it.
The rollup reads ONE shared Teams channel that teammates post to. The channel is not hard-coded.
config/team_config.json. If team_id or channel_id is blank, this is a first run.AskUserQuestion to ask the user to paste the **link of theTeams channel** where the team posts its Cowork Dashboard stats (in Teams: channel ⋯ → *Get link to
channel*). This must be the same channel cowork-dashboard-member posts to.
python scripts/resolve_channel.py --link "<pasted url>" --config config/team_config.json
It extracts channel_id (the 19:…@thread.… segment) and team_id (the groupId), writes them
plus channel_link back to the config, and every later run reads the same place. Optionally call
GetTeam(team_id) and save its displayName as team_name so the dashboard header is named.
(ChannelMessage.Read.All). This skill does not create Teams or channels. If a read fails with an
authorization error, the runner isn't a member — add them in Teams; the skill can't grant access.
On later runs team_id + channel_id are already set, so skip straight to the workflow. Re-ask for a
link only if the user wants to point at a different channel.
Read config/team_config.json. If team_id/channel_id are blank, run First run above.
ListChannelMessages(team_id, channel_id, top=50) (paginate with next_link if the team is chatty).
Save the returned value array verbatim to working/raw_messages.json (the parser accepts the Graph
message shape directly). The latest-15-days window and latest-post-per-person dedupe are
applied in step 3 — don't hand-filter here.
python scripts/parse_posts.py --in working/raw_messages.json --config config/team_config.json \
--out working/team_data.json --window-days 15 [--now YYYY-MM-DD] [--generated YYYY-MM-DD]
parse_posts.py:
--window-days, anchored to --now/today), then keepsthe latest post per sender (id hashed away, never stored/shown), numbering contributors 1..N;
process_groups.json) and canonicalizes skills(skills_vocabulary.json + skill_aliases.json);
working/team_data.json (meta + one snapshot + members[] with role|null + metrics).python scripts/build_outputs.py --in working/team_data.json --config config/team_config.json
One invocation renders BOTH deliverables, so the manager approves the build once, not twice:
output/cowork-team-roi-dashboard.html — self-contained HTML (no external assets). Five smalltabs: Overview (auto-insights + KPI band) · Impact & Value (pillars, categories with $,
roles, deliverables by format) · How Cowork is used (business-process accordion — each process
expands to its distinct deliverables with the file format inline + the skills behind them —
category mix, analyzed → produced) · Trends (minimal fortnight-over-fortnight line) ·
Glossary & method. Value = hours × rate, recomputed live by a rate control.
output/how-to-read-team-roi-dashboard.pdf — a single landscape interpretation guideexplaining every KPI, the five tabs, the two controls, the privacy model, and the methodology, so
recipients can read the dashboard unaided (uses reportlab, bundled in the Cowork container).
build_outputs.py just drives the two builders in sequence; build_dashboard.py and
build_guide_pdf.py still run individually if you ever need only one.
The email send is deliberately not bundled into step 4: building the files is one approval, and
sending them to people is a second, distinct approval. If email_on_run is true (default) and the
user hasn't said "don't send":
ListChannelMembers(team_id, channel_id) → resolve each to anemail/UPN; de-duplicate; include the runner. (A standard channel returns the team members — that's
the intended audience.) Never add anyone outside the channel.
figures from working/team_data.json — sum the members' headline metrics (time saved, value =
hours × rate, sessions, run tasks, deliverables) and name the top 1–2 business processes. Do not
hand-invent numbers; if a figure isn't in the data, omit it.
SendEmailWithAttachments(
to=<resolved channel-member emails>,
subject="Team Cowork Dashboard — latest rollup (<period>)",
content_type="HTML", body=<summary html>,
direct_attachment_file_paths=["output/cowork-team-roi-dashboard.html",
"output/how-to-read-team-roi-dashboard.pdf"])
In interactive runs the platform's approval dialog is the confirmation; scheduled runs send
automatically. The "Powered by Copilot Cowork" footer is appended by the host — don't add your own.
Glob output/cowork-team-roi-dashboard.html and Glob output/how-to-read-team-roi-dashboard.pdf to
confirm both exist, then tell the user they're saved and the email went to the channel members.
Optionally show a 3-line highlight (time saved, value, top process) — aggregate only.
If the user asks, SetupScheduledPrompt with a self-contained description: *"Read the last 15 days of
Cowork Dashboard posts in the team channel, aggregate them into the anonymized team dashboard and one-page
PDF guide, save them to my files, and email them to the channel members."*
Timing: the Member skill posts on a biweekly Monday cycle, so schedule the manager rollup to
run 1–2 days later — on Wednesday — which gives teammates Monday and Tuesday to post before the
rollup reads the channel. Use frequency Week, interval = cadence_days / 7 (= 2 → every other
Wednesday), weekDays=["Wednesday"], hours=["9"], name "Cowork Dashboard team dashboard". Scheduled runs
build both outputs and email the channel members automatically (no interactive approval).
privacy_k_threshold(default 3) contributors share that Role; otherwise those contributors collapse into a single
combined bar. Small homogeneous teams usually render one combined bar — that's correct.
prompts, or JTBD prose. The parser doesn't read them and the dashboard can't show them.
never who did what.
This skill can only aggregate what cowork-dashboard-member posts. These must match its copies or
aggregation breaks — change them in both bundles together:
| Item | This skill | Must match |
|---|---|---|
| Business-process grouping | scripts/process_groups.json | cowork-dashboard-member/scripts/process_groups.json (and cowork-roi-report's copy) — byte-for-byte |
| Skills vocabulary | scripts/skills_vocabulary.json | cowork-dashboard-member/scripts/skills_vocabulary.json (and cowork-roi-report's) — byte-for-byte |
| Roles taxonomy | scripts/roles_taxonomy.json | cowork-dashboard-member/scripts/roles_taxonomy.json — byte-for-byte |
| Value pillars | references/value-pillars.md | cowork-dashboard-member/references/value-pillars.md — byte-for-byte |
| Role attribute + no country/names | reads the post header Role: line | cowork-dashboard-member/scripts/format_member_message.py (emits Role:; excludes country/names) |
| Deliverable → process link + by-type rollup | parser reads the "By type" deliverables table | format_member_message.py (that rollup "is also what the aggregated Dashboard reads") |
| The shared channel | channel_id resolved from the link the user pastes | the channel cowork-dashboard-member posts to (its SKILL.md → *Target channel*) — must be the same channel |
scripts/skill_aliases.json is a reader-only compatibility shim (maps older non-canonical skilllabels → the vocabulary). It is not part of the shared contract and lives only here.
Type/Date/Process/Skills/Hours/Value — no names). To show doc names, the Member skill must first add
an opt-in name manifest; only then can this reader surface a manager-only named list. That is a
Member-skill change — flag it before promising named deliverables.
an empty 15-day window yields no dashboard, not a made-up one.
parse_posts.py totals; build_dashboard.py prices at the live rate; the emailfigures are read from team_data.json.
recent post per sender; re-running a member's post replaces their earlier contribution.
a different one. Never guess or construct a channel_id.
aggregate-only. Interactive sends go through the approval dialog; honor "don't email" / email_on_run:false.
SKILL.md, README.md, CHANGELOG.mdconfig/team_config.json — rate + k-threshold + cadence + 15-day lookback + email toggle; thechannel IDs are filled in on first run (not shipped hard-coded).
scripts/resolve_channel.py — parse a pasted Teams channel/message link → team_id + channel_id; persist to config (stdlib only).scripts/parse_posts.py — channel posts → anonymized team_data.json (stdlib only; 15-day window, latest-per-sender, groups processes, canonicalizes skills, k-anon-ready).scripts/build_dashboard.py — team_data.json → self-contained HTML dashboard (stdlib only).scripts/build_guide_pdf.py — one-page landscape interpretation PDF (uses reportlab, bundled in Cowork).scripts/build_outputs.py — one step that builds BOTH the dashboard and the PDF guide (single approval; drives the two builders above).scripts/process_groups.json, scripts/skills_vocabulary.json, scripts/roles_taxonomy.json — mirrors of the Member bundle (shared contract).scripts/skill_aliases.json — reader-only skill-label compatibility shim.references/value-pillars.md — mirror of the four-pillar crosswalk.examples/sample_raw_messages.json — two real, de-identified posts for an offline end-to-end test.Take microsoft/coworkdashboard-cowork-dashboard-team-dashboard 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.