| name | session-cost-summary |
| description | Reads the real /cost (Copilot-credit) usage of your past Copilot Cowork sessions by driving your
signed-in Cowork web app, then builds a Session | Date | /cost table with a running total. Asks the
date window (default last 30 days), opens Cowork, enumerates your sessions from the left nav, and for
each in-window session types /cost and reads "N credits used for this task so far". No cost is ever
computed or estimated. Banks each reading as it goes so interruptions never lose progress, and
honestly reports sessions that predate credit tracking or are awaiting your input.
Use when the user says "cowork cost report", or asks to "summarize my session costs", "run /cost on my
past sessions", "total my Cowork credit usage this month", or "cost per session since <date>".
Do NOT use for: Azure/M365 billing or invoices, Power BI cost reports, GitHub Copilot/IDE usage, or
the What-Cowork-Did-For-Me value/ROI report (use cowork-roi-report).
|
| cowork | {"category":"analysis","icon":"Money"} |
Session Cost Summary — read real /cost across your Cowork sessions
Produces one table — Session · Date · /cost · Running total — where every number is a genuine
/cost reading pulled live from the Cowork web app. This skill never computes, estimates, or
fabricates a cost. The only authoritative source of a session's Copilot-credit cost is the /cost
command inside that session in the Cowork UI, so the core of this skill is a browser sweep: it
drives the user's signed-in Cowork app, opens each session, runs /cost, and reads the result.
When to use
- "Total my Cowork credit usage since " / "running credit cost to date"
- "Run /cost across my past sessions and add it up" / "cost per session this month"
When NOT to use
- Cloud/M365/Azure billing or invoices, Power BI cost dashboards → separate billing sources
- GitHub Copilot / IDE usage → not Cowork sessions
- A time-saved / value / ROI narrative →
cowork-roi-report (this skill is cost-only)
What you need
- The local browser tool (
browser_actions, a.k.a. simple_browser) — this skill drives the user's
own signed-in browser. It does not work from a headless agent with no browser.
- The user signed into Cowork at
https://m365.cloud.microsoft/agents/cowork (or
https://copilot.cloud.microsoft/cowork).
If the browser tool is unavailable, say so plainly and fall back to: read any already-captured
readings from the ledger (scripts/cost_rollup.py), and let the user paste /cost blocks
(cost_rollup.py --paste ...). Never pretend a sweep ran.
Workflow
1. Ask the date window FIRST (one AskUserQuestion — always present options, never just run)
"Which period should I total?" with options:
Month to date, Last 4 weeks, Last 30 days (default), Last 7 days, + free-text YYYY-MM-DD.
- Month to date = 1st of the current month → today. Last 4 weeks = 28 days ago → today.
Resolve
since/until in the user's local time zone.
1b. Give the "go do other things" heads-up, then work in the background
The moment the period is chosen, tell the user plainly — this is required, not optional:
"This opens each of your sessions and reads /cost one at a time, so a full month can take several
minutes. Feel free to go do other things — I'll keep working in the background and have the table ready
when you're back."
Then proceed without waiting for further input.
2. Open Cowork in the browser (reliably — avoid the full-reload hang)
- First
create_tab to a light page (e.g. https://example.com) — create_tab straight to the heavy
Cowork URL frequently heartbeat-times-out on load.
- Then
navigate_to https://m365.cloud.microsoft/agents/cowork.
- Read the page (
get_dom); if it shows a sign-in wall, hand control over with
request_browser_interaction (reason_type security_verification) so the user signs in, then continue.
3. Enumerate the sessions (once)
get_dom the home view and read the left-nav "Recent tasks" list (aria-label="Recent tasks").
Each <a> has the session title and a stable data-row-id (the permanent session id). This is
the authoritative list the user sees — it includes chat-only sessions an OneDrive scan would miss.
Keep sessions whose date is in the window. Get dates from the OneDrive Documents/Cowork folder
(GetDefaultDrive → GetDriveChildren; folder names are <goal-slug>-<YYYY-MM-DD>) where present;
chat-only sessions have no folder date — label them "recent" and still include them. Also record each
session's last_modified (its OneDrive folder lastModifiedDateTime) — used next to decide reuse vs re-sweep.
3b. Reuse cached costs — only re-sweep sessions that changed (the cache)
Every /cost reading is banked with the session's last-activity time, so a re-run doesn't re-extract
unchanged sessions. Write the in-window sessions to working/sessions.json as
[{"key":"<data-row-id>","name":"<title>","date":"<YYYY-MM-DD|recent>","last_modified":"<ISO>"}], then:
python scripts/cost_rollup.py --plan --sessions-file working/sessions.json
It returns {"reuse":[…cached readings…], "resweep":[…]}.
- Only sweep the
resweep set — sessions with no cached cost, OR whose last_modified is newer than
when we last read /cost (i.e. new conversation since), OR whose freshness can't be verified.
- Reuse the cached number for everything in
reuse — do NOT reopen those sessions.
On a first run everything is a re-sweep; on later runs only new/changed sessions are opened.
4. Sweep /cost per session (the fast, proven pattern) — only the resweep set
The composer input box sits at a fixed screen position, so capture its coordinates once and
reuse them — do NOT re-read the whole page each session (that is the main time sink). Once, take a
get_screenshot, extract screenshot.base64Image from the returned browserState, view it, and note
the "Message Cowork" box centre (≈ x=475, y=475 at a 1024×768 capture — rescale to the real
width/height in the payload).
Then for each in-window session:
- Open it via the left-nav click —
click the session's row in the left column (≈ x=55, at its
row y). This is an in-app route swap; do not navigate_to each session — that fully reloads the
app and renders the entire chat history, which is what hangs on long sessions. The click changes the
URL, so it returns a url-changed "error" — that's expected; the nav still happened.
- Settle + confirm —
wait ~2s then get_screenshot; confirm the tab URL ends with the target
data-row-id. (This also refreshes the tool's page state so the next click won't throw page-changed.)
- Run /cost —
click the composer coordinate, type /cost, keypress ["Enter"] twice (the app
turns /cost into a command chip on the first Enter; the second submits it), wait 3s, get_screenshot.
- Read the result — extract the screenshot, view it, and read the line
"N credits used for this task so far." That N (AiCredits) is the session's cost.
- Bank it immediately, with freshness metadata — before moving on:
python scripts/cost_rollup.py --paste "<the /cost line>" --session <data-row-id> \
--date <YYYY-MM-DD> --label "<title>" --session-modified <last_modified>
This stores the reading plus captured_at and the session's last_modified, so a future run can skip
this session if it hasn't changed. Banking every reading as you go is also the resilience mechanism.
5. Handle the exceptions (report honestly — never guess)
- "…started before we began tracking credit usage, so a total isn't available." → mark the session
not tracked; there is no number to report.
- Open question card / "Needs your input" (an approval or choice card sits where the composer would
be, no text box) → "couldn't extract cost — session is awaiting your input." Do not click
Skip/answer to force it (that discards the user's pending decision) unless the user explicitly says to.
- Pending email/tool draft with the composer still visible below it →
/cost runs fine and does
not discard the draft; capture normally. (Only a message you send would discard it — /cost in
the app is a command, and in practice the draft was preserved.)
- Any browser error (
page-changed, url-changed mid-batch, heartbeat timeout, session
disconnect, "No such tool available") → skip-and-return: log the session as pending, move to the
next, and retry the skipped ones at the end. Never retry the same failing step in a tight loop.
5b. Count deliverables per session (parallel OneDrive — NOT the browser DOM)
For each folder-backed in-window session, list its output/ folder to get the files it produced — with
GetDriveChildren in parallel batches. Do not read this from the browser DOM: that reintroduces
the heavy per-session payload the screenshot flow avoids; OneDrive is a cheap side-channel (~a few seconds
for the whole set, off the critical path). Path: Documents/Cowork/Tasks/<goal-slug>-<YYYY-MM-DD>/output.
- Fallback if
output/ 404s: some sessions store artifacts at the session-folder root — SearchDrive
for the folder, or list the session folder itself.
- Chat-only sessions have no folder → no deliverables (renders
—); that's accurate, not a miss.
Write { "<session-key>": [<filenames>], ... } (key = the same key you banked the cost under) to
working/deliverables.json, then classify + fold:
python scripts/cost_rollup.py --deliverables-file working/deliverables.json
Folding rules (so counts read true, not "22 files"): images → screenshots; -v\d+ zips and
-7day/-sample/-60d html → variants; (conflicted…) copies and .md/.txt/.json → support — all folded
out. Only primary files remain, grouped by type and shown by name.
6. Aggregate + present (relay the canonical output — do NOT re-format)
Bank all readings, then render the report with the fixed renderer and relay its output verbatim:
python scripts/cost_rollup.py --since <since> --until <until> --markdown
This emits a byte-for-byte identical structure every run (proven). Do not rebuild the table by hand.
Then append the fixed coverage note + exception lists (see Output format). For 4+ rows you may add a
render_ui card (via the render-ui skill) in addition to — never instead of — the canonical table.
7. Export & share (offered on every report — act on the user's reply)
The --markdown output ends with a fixed offer to export or email. Handle the choice:
8. Going forward (the cache does the work)
Because every reading is stored with the session's last-activity time, re-running this skill later only
re-sweeps sessions that had new conversation since — everything unchanged is reused instantly, so repeat
runs are fast. Tell the user how many were reused vs freshly swept. Offer SetupScheduledPrompt for a
recurring roll-up.
Resilience (build this in — it is the difference between finishing and hanging)
- Bank every reading the instant you read it (append to the ledger / a capture JSON). Then a
disconnect, timeout, or interruption never loses progress — resume from the un-captured sessions.
- Skip-and-return: on any per-session failure, skip, log, continue; retry skipped at the end.
- Cap the wait per session (a few seconds). If a session won't load or the composer won't appear in
time, report "couldn't extract — timed out" and move on. Do not wait indefinitely.
- Reuse the composer coordinate; read results from screenshots, not full-page DOM pulls.
- Left-nav click, not full navigation, to avoid re-rendering each session's whole history.
Output format (FIXED — same every run; never re-format the table)
The table + total come from cost_rollup.py --markdown (relay verbatim). Its structure is invariant:
- A title line:
## Cowork cost report — <since> to <until>
- A table with exactly these columns in this order:
Date | Session | /cost (AiCredits) | Running total | Deliverables, rows sorted by date ascending.
Credits are whole AiCredits (no decimals); dates use non-breaking hyphens so they never wrap.
The Deliverables cell groups the session's PRIMARY output files by type, named —
**PDF:** a.pdf · **HTML (2):** b.html, c.html · **ZIP:** d.zip — with folded noise summarized as
_+N screenshots, M variants_. A session with no saved files shows —. (No per-deliverable cost is
shown — credits ÷ the list lets the reader infer it.)
**Total: N AiCredits (K sessions)**
- An
_Captured but undated_ list, only if any.
- A footer offering export to slides / email me this (the renderer appends it; act on the reply per step 7).
Then append, in this fixed order and wording (each line only if non-empty):
_Not tracked (predate credit tracking):_ <names>
_Awaiting your input (couldn't extract):_ <names>
_Reused from cache / freshly swept: X / Y_
Do not add/remove/rename columns, change the total's wording, reorder sections, or introduce ✅/emoji or a
"Source" column on some runs but not others. The layout is identical whether it's 2 sessions or 40.
Guardrails
- Real /cost only — never compute or estimate. Every value is the number
/cost printed. If a
session can't be read, show why (not tracked / awaiting input / skipped) — never invent a figure.
- Don't disturb the user's pending work. Never click Send/Skip/answer on an open approval or
question card to reach the composer unless the user explicitly authorizes it.
- Be honest about the browser. If the browser tool isn't available in this environment, say so and
use the ledger/paste fallback — do not claim a sweep you didn't run.
- Privacy & per-user. Show session titles, dates, and cost figures only — never session contents.
Readings are the invoking user's own; nothing is shared.
Bundled files
scripts/cost_rollup.py — reads/writes the durable /cost ledger, windows by date, computes the
running total, and ingests pasted or swept /cost readings (--paste, --ingest-file). Parses
/cost text with parse_cost_output; does no cost computation of its own.
scripts/build_cost_deck.js — the default "export to slides" deck generator. Data-driven: reads the
report JSON and builds the standard KPI-cover + cost-by-session table deck (named grouped deliverables,
auto-pagination, signature footer). Keeps every export's structure identical.
scripts/capture_cost_browser.py — an alternative standalone Playwright sweep for environments where
the agent's browser tool isn't available but Python + the user's login are (runs on their machine).
Durable files (per-user, persist across sessions)
/mnt/user-config/.claude/cowork-session-credits.json — AiCredits /cost readings (the ledger).
/mnt/user-config/.claude/cowork-session-costs.json — USD /cost readings (statusLine, if present).
/mnt/user-config/.claude/cowork-session-telemetry.json — session dates + titles (join key).