| name | reports-morning-briefing |
| description | ☕ BRIEFING: Morning catch-up — scan Slack, Jira, Fireflies, Gmail & Google Calendar to produce a scannable briefing of what happened since you last worked, plus a prep rundown of today's meetings with sourced context. Perfect for Monday mornings, after a holiday, or at the start of any workday. Triggers: "what did I miss", "catch me up", "morning briefing", "what happened while I was away", "what's new since Friday", "co się działo", "co powinienem wiedzieć", "zrób mi briefing", "poranny briefing", "co nowego", "nadgonię", "co pominąłem", "zacznijmy dzień", "prepare me for today's meetings", "what's on my calendar today", "jakie mam dziś spotkania", or any variation of wanting a situational summary before starting work. Also trigger when someone says "good morning", mentions it's Monday, or asks to "start the day". Scans all sources automatically — no project required. Output: complete digest of everything important, no fluff, only signal. |
Morning Briefing Skill
Purpose
Produce a scannable "morning coffee" catch-up covering everything important the user needs
to know before starting work. No project scope required — this is a personal digest.
Tone: Direct. Warm but not chatty. Like a trusted colleague summarising things for you.
Length: As long as it needs to be, as short as it can be. Every bullet must earn its place.
Format: Scannable sections, bullet points, bold for urgency only.
Step 0: Check for user-specified context (optional)
Do NOT ask before scanning — go straight to Step 1.
Only pause if the user's message explicitly specifies a scope or window (e.g. "catch me
up since Monday", "only check Slack and Jira", "what happened in the last 3 days").
In that case, use those constraints directly — no confirmation needed.
If the user says "morning briefing" or similar with no other context — skip elicitation
entirely and scan everything with the defaults below.
Optional — only if the user explicitly asks to customise scope:
Use ask_user_input_v0 with a single Focus question (multi-select):
- Slack messages & mentions
- Jira tickets
- Meetings (Fireflies)
- Emails (Gmail)
- Today's meetings & prep
Default when no focus is specified: scan all sources.
Step 0.5: Resolve user identity
Resolve the user's identity across tools where needed, but do not block unrelated
scans on Fireflies-specific identity lookup. Carry the results into subsequent steps.
- Slack user ID: Resolve dynamically by calling
slack_search_users with the user's
name or email (obtained from fireflies_get_user() or Google Calendar). Store the
returned user_id as <slack_user_id> and use it for all to:<@USERID> queries.
If resolution fails, fall back to searching by name only and note the uncertainty.
- User email (for Fireflies): Try to resolve by calling
fireflies_get_user() to get
the authenticated user's email address. Store this as <user_email> for use in the
Fireflies participants filter. If fireflies_get_user() fails, fall back to
inferring the email from today's Google Calendar attendee list (the user's own entry).
If neither method works, set <user_email> as unavailable — skip Fireflies and note
the limitation at the bottom of the briefing. Do not block other sources.
- Jira:
currentUser() resolves automatically — no action needed.
- Gmail: Primary account — no action needed.
Proceed to Step 1 as soon as Slack/Jira/Gmail/Calendar can run. <user_email> is
required only to narrow Fireflies by participant — it must not gate the rest of the scan.
Step 1: Scan all sources in parallel
Run all of the following simultaneously. Each source has its own window logic — there is
no single global cutoff date.
Slack — recent activity & mentions
Window logic:
- If today is Monday or the day after a public holiday → use a cutoff date covering roughly the last 3 days
- If the user's message implies a longer absence (e.g. "I'm back from holiday", "what did I miss this week", "I was away since Thursday") → extend the window to cover the full stated absence; if the length is unclear, default to 5 days and note at the bottom that older messages may exist
- Otherwise → use a cutoff date covering roughly the last 1 day
- If user specified an explicit window → use that instead, overriding all of the above
slack_search_public_and_private(
query="to:<@USERID> after:YYYY-MM-DD",
limit=30, sort="timestamp"
)
Also search for high-signal keywords across all channels:
slack_search_public_and_private(
query="urgent OR blocker OR asap OR help after:YYYY-MM-DD",
limit=20
)
For any message where the user was @mentioned or that contains high-signal keywords,
read the full thread — replies often contain the resolution:
slack_read_thread(channel_id="<id>", thread_ts="<ts>")
Extract: anything requiring a response, decisions made without the user, blockers raised,
key announcements.
⚠️ MANDATORY before flagging any message as "awaiting response":
For every candidate message (DM, @mention, keyword hit, or channel message directed at the user),
you MUST verify the user hasn't already replied before including it. Do both checks:
slack_read_thread(channel_id, thread_ts) — check for a reply in the thread
slack_read_channel(channel_id, limit=20, oldest=<message_ts>) — read the channel/DM
forward from that message to catch replies sent outside the thread
Only flag as "awaiting response" if neither check shows a clear on-topic reply from the user.
Do NOT skip this step. Flagging a message as unanswered without verifying is a known
failure mode. If the DM channel can't be read, note uncertainty rather than flagging as open.
Jira — recently updated & newly assigned tickets
Window logic: Two queries, both with a 48h window:
# Recently updated tickets assigned to the user
JQL: assignee = currentUser()
AND updated >= "-2d"
AND status NOT IN (Done, Closed)
ORDER BY updated DESC
fields: summary, status, priority, issuetype, updated
maxResults: 30
# Newly assigned tickets (catches tickets assigned but not yet touched)
JQL: assignee = currentUser()
AND created >= "-2d"
ORDER BY created DESC
fields: summary, status, priority, issuetype, created
maxResults: 10
If user specified a custom window → substitute "-2d" with the appropriate value.
Extract: newly assigned work, meaningful status changes, anything blocked or high-priority,
anything with an approaching deadline. Skip routine "still In Progress, nothing changed" items.
Fireflies — recent meetings
Window logic: Last 7 days. If user specified a shorter window, use that.
fireflies_get_transcripts(
fromDate: "<7-days-ago>",
limit: 10,
participants: ["<user_email>"]
)
The participants filter ensures only meetings the user actually attended are returned —
no post-fetch filtering needed. For each returned meeting, call fireflies_get_summary
and extract: decisions made, action items assigned to the user, open questions, anything
requiring follow-up.
Gmail — unread inbox
Window logic: All unread messages in inbox, no date filter.
search_threads(
query="is:unread in:inbox",
maxResults: 30
)
Skim subject lines and senders. Flag: anything from a client, anything with "urgent" /
"action required" / "deadline" in subject, direct replies awaiting response, calendar
invitations requiring a decision.
Ignore: newsletters, automated notifications with no action needed, marketing emails.
Grouping rule: Multiple emails of the same low-priority type → collapse into one
grouped bullet. Always collapse the following — never list individually:
- Automated CI/CD notifications (GitHub Actions, Sentry alerts, build results, etc.)
- Calendar accept/decline/maybe replies
- Recruitment or HR platform notifications (Elevato, LinkedIn, Pracuj, etc.)
- Newsletter or marketing emails (even if unread)
- Monitoring or uptime alerts that require no manual response
If unsure whether an email is important, include it as a single collapsed line rather
than a full bullet. When in doubt: "3 unread notifications from GitHub — no action
needed" is enough.
Step 1b: Google Calendar — today's meetings
Always run this step. Not gated on any user selection.
Fetch all meetings for today:
Google Calendar:list_events(
startTime: "<today>T00:00:00",
endTime: "<today>T23:59:59",
timeZone: "<user's local timezone, e.g. Europe/Warsaw>",
orderBy: "startTime"
)
Filter out: all-day events, events the user declined, solo/no-attendee blocks.
For each remaining meeting, note: title, start time, attendees (names preferred over emails).
Then, for each meeting, run these lookups in parallel — strictly source-based, no fabrication:
a) Fireflies — last session with same title or overlapping attendees:
# Primary: search by core keyword across all content
fireflies_get_transcripts(keyword="<core project keyword>", scope="all", limit=3)
# Fallback (if 0 results): search by participant overlap with today's attendees
fireflies_get_transcripts(participants=["<attendee1@email>", "<attendee2@email>"], limit=3)
Use the most recent result. Call fireflies_get_summary on it. Extract:
- 1-sentence outcome summary
- Any action items assigned to the user that appear unresolved
Core keyword = the most distinctive word from the meeting title (e.g. "ProjectX
Refinement" → "ProjectX"). Avoid generic words like "daily", "sync", "meeting".
b) Jira — open tickets for the implied project:
Derive a project keyword from the meeting title (e.g. "Acme daily" → Acme,
"ProjectX Refinement" → ProjectX). Search:
# Primary: use derived project key
JQL: project = "<derived_key>" AND status NOT IN (Done, Closed)
AND (assignee = currentUser() OR comment ~ currentUser())
ORDER BY priority DESC
maxResults: 5
# Fallback (if 0 results or key lookup fails): search by keyword in summary
JQL: assignee = currentUser()
AND status NOT IN (Done, Closed)
AND summary ~ "<keyword>"
ORDER BY priority DESC
maxResults: 5
Surface: Blocked tickets, High/Critical priority, items In Review. Skip routine In-Progress.
If no project key can be confidently derived, skip the primary query and go straight to
the fallback. If both return nothing, note "No Jira context found." — do not invent.
c) Slack — recent thread for this project (last 3 days):
slack_search_public_and_private(
query="<project name> after:<3-days-ago>",
limit=5, sort="timestamp"
)
Extract only: unresolved questions, blockers, or messages directed at the user.
If all three lookups return nothing — show the meeting bare (time + title + attendees)
with note: "No prior context found." Do NOT invent talking points or summaries.
Step 2: Synthesise
Before marking any Slack message as "awaiting response" — this verification MUST
have already been done during Step 1 scanning (see ⚠️ block above). Do not reach
Step 2 with unverified candidates. If a message slipped through without verification,
run the checks now before including it:
- The thread under that message:
slack_read_thread(channel_id, thread_ts)
- The channel/DM history from that message onwards:
slack_read_channel(channel_id, limit=20, oldest=<message_ts>)
Only count a subsequent message as a reply if it clearly addresses the sender or
topic of the original (e.g. direct @mention of the sender, or an on-topic response).
An unrelated message posted later is not a reply. When in doubt — omit the item
rather than falsely flagging it as awaiting response.
Cross-reference all sources. Apply this priority filter:
Include:
- Action items assigned to the user
- Decisions made that affect the user's work
- Messages / emails awaiting a response from the user
- Ticket changes that are meaningful (new assignment, status shift, blocked, urgent)
- Meetings with relevant outcomes or action items for the user
- Anything with a deadline in the next 48 hours
- Any notable context the user needs before today's meetings
Exclude:
- Routine updates with no user impact
- Tickets that changed status but need no action
- Slack noise (emoji reactions, "thanks", casual banter)
- FYI emails with no required action
- Meeting summaries where the user had no action items
Grouping rule: Multiple low-priority items of the same type → one grouped bullet.
Example: "4 new (Senior) Delivery Manager applications — Iurii Klova, Magdalena Nowak,
Sebastian Markowicz, Michał Olędzki. Review in Elevato."
No bullet caps. Include every item that passes the filter above.
When in doubt about an item's importance: omit. Signal only.
Output Format
☕ Morning Briefing — [Day, Date]
**🔴 Needs your attention**
• [Urgent item — what it is, who it's from, what's needed]
• [Another item requiring action or response]
**📬 Messages & emails**
• [Slack/email requiring response — from whom, about what]
**📋 Your tickets**
• [PROJ-123] [Title] — [status or why it matters]
**🗣️ While you were away**
• [Meeting name] ([date]) — [1-sentence outcome or action item]
**📅 Coming up today**
• [Deadline or time-sensitive item]
**🗓️ Today's meetings**
[One block per meeting — see format below]
**✅ Proposed to-dos**
• [Short imperative task — sourced from above]
Section Rules
🔴 Needs your attention — blockers, urgent requests, overdue items, anything explicitly
asking for the user's input. No bullet cap. If nothing urgent: omit this section.
📬 Messages & emails — Slack threads or emails awaiting a response. Write as:
[Person] asked about [topic] in [#channel / email]. Group batches of similar low-priority
emails into one line. If nothing actionable: omit.
📋 Your tickets — only tickets updated or created in the last 48h that changed
meaningfully. Skip routine "still in progress" items. No bullet cap. If nothing: omit.
🗣️ While you were away — meetings in the last 7 days where the user was a participant
and outcomes or action items exist. One bullet per meeting, one sentence max.
Skip meetings with no relevant outcomes. Omit section if nothing qualifies.
📅 Coming up today — concrete deadlines or time-sensitive items due today or tomorrow.
Omit if nothing found.
🗓️ Today's meetings — always shown if there are meetings today. Render one block per
meeting, in chronological order. If no meetings today: omit the section entirely — do
not write "No meetings today."
🕙 [HH:MM] [Meeting Title]
👥 [Attendee 1, Attendee 2, + N others]
📝 Last time: [1-sentence Fireflies summary — or "No transcript found."]
✅ Your open items: [unresolved action items from last session — omit if none]
🔴 Blocked ticket: [PROJ-123 title — only if blocked/critical — omit if none]
💬 Slack: [1-line unresolved thread — omit if nothing relevant]
Rules:
- Never invent summaries, action items, or talking points. Every line traces to a tool result.
- Attendees: use display names, not raw emails. Max 3 named + "+ N others".
- Meeting blocks with no context show bare title/time/attendees + "No prior context found."
✅ Proposed to-dos — a consolidated list of concrete actions for the user today,
distilled from everything found in the briefing. Written as short imperative tasks.
Rules:
- Every item must trace directly to a real finding from the scan — no invented tasks
- Format:
[Verb] [object] — [context], e.g. "Reply to Mateusz re: deployment blocker in #project-x" or "Review PROJ-123 before standup"
- Max 7 items; if more qualify, group the lowest-priority ones into a single line
- Omit this section only if nothing actionable was found across all sources (rare)
- Do not duplicate items already obvious from 🔴 Needs your attention — use that section for urgent items; use ✅ for the full planned task list including non-urgent items
Sections with nothing to report are omitted entirely — never write "Nothing to report."
Scan Caveats
If a source fails or returns no data, add a brief note at the bottom of the briefing:
*(Gmail not scanned — no access)*
If Jira returns 0 results for assignee = currentUser(), note:
*(Jira returned no assigned tickets — account may not be linked or no active work found.)*
If scanning reveals a long absence (5+ days), add one line:
*(Extended absence — some older items may have been resolved. Check manually if unsure.)*
Quality Checks
Before presenting, verify:
After Presenting
If any items are ambiguous, offer to pull more detail:
"Want me to pull up [the thread / that ticket / the meeting notes] for more context?"
Never post, reply, or action anything automatically. Briefing is read-only.