
Morning Briefing
- 195 installs
- 5 repo stars
- Updated March 14, 2026
- krishagel/geoffrey
Helps with ai & agent building tasks.
About
morning-briefing is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- morning-briefing
- AI & Agent Building
- AI-coding skill
Morning Briefing by the numbers
- 195 all-time installs (skills.sh)
- Ranked #2,893 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/krishagel/geoffrey --skill morning-briefingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 195 |
|---|---|
| repo stars | ★ 5 |
| Last updated | March 14, 2026 |
| Repository | krishagel/geoffrey ↗ |
What it does
Helps with ai & agent building tasks.
Files
Morning Briefing Workflow
Overview
Generates a daily morning briefing and delivers it three ways: 1. Terminal: Summary displayed immediately 2. Obsidian: Full briefing saved to daily note 3. Email: Briefing text + audio podcast attachment
Location
- Gig Harbor, WA - Use for weather queries
Phase 1: Gather Data
Execute these data gathering steps:
1.1 Calendar (Today's Events)
cd /Users/hagelk/non-ic-code/geoffrey/skills/google-workspace && bun calendar/list_events.js psd --todayReturns JSON with today's events including:
- Event summary, location, start/end times
- Attendees and response status
- Hangout/meet links
Account: Use psd for work calendar
1.2 OmniFocus Tasks (Due & Flagged)
osascript -l JavaScript /Users/hagelk/non-ic-code/geoffrey/skills/morning-briefing/scripts/get_due_flagged.jsReturns tasks that are:
- Due today or overdue
- Flagged
- Available (not blocked, not deferred to future)
1.3 Recent Emails (Last 24 Hours)
# Get yesterday's date in Gmail query format
YESTERDAY=$(date -v-1d +%Y/%m/%d)
cd /Users/hagelk/non-ic-code/geoffrey/skills/google-workspace && bun gmail/list_messages.js psd --query "in:inbox after:$YESTERDAY" --max 15Returns recent inbox messages with:
- From, subject, date, snippet
- Whether read or unread
- Thread ID for context
- Labels array
Filtering: Only show emails still in inbox (not already labeled/processed).
- Emails with custom labels (Label_XXXXX) have been sorted and should be excluded
- Exception: Include emails with SaneCC or SaneLater labels (worth knowing about)
- System labels (UNREAD, INBOX, CATEGORY_*, IMPORTANT) don't count as "processed"
Philosophy: Any email still in inbox from last 24 hours needs attention, read or not.
Account: Use psd for work email
1.4 Open Freshservice Tickets
bun /Users/hagelk/non-ic-code/geoffrey/skills/freshservice-manager/scripts/list_tickets.js '{"workspace_id": 2, "filter": "new_and_my_open"}'Returns open tickets assigned to or created by user in Technology workspace.
1.5 Pending Approvals
bun /Users/hagelk/non-ic-code/geoffrey/skills/freshservice-manager/scripts/get_approvals.js requestedReturns service requests awaiting approval.
1.6 Weather
Use WebSearch:
Gig Harbor WA weather today forecastExtract:
- Current conditions
- High/low temperature
- Precipitation chance
- Any alerts
1.7 EdTech News (with Synopses)
Use WebSearch:
K-12 education technology news past 24 hours January 2026For each article found (3-5 articles): 1. Use WebFetch to read the full article 2. Extract a 2-3 sentence synopsis covering:
- What happened / what's new
- Why it matters for K-12 education
- Key takeaway or action item
Freshness check: Verify article dates before including. Only include articles published within the last 24 hours. Skip older articles even if they appear in results.
Topics to cover:
- EdTech product launches and updates
- School technology policy changes
- Cybersecurity in schools
- Digital learning trends
1.8 AI News (with Synopses)
Use WebSearch:
artificial intelligence news past 24 hours January 2026 latestFor each article found (3-5 articles): 1. Use WebFetch to read the full article 2. Extract a 2-3 sentence synopsis covering:
- What's the development
- Industry impact
- Relevance to education/work
Freshness check: Verify article dates before including. Only include articles published within the last 24 hours. Skip older articles even if they appear in results.
Topics to cover:
- Major AI model releases and capabilities
- AI policy and regulation
- Enterprise AI adoption
- AI research breakthroughs
1.9 K-12 Leadership News (with Synopses)
Use WebSearch:
K-12 school leadership superintendent news past 24 hours January 2026For each article found (2-3 articles): 1. Use WebFetch to read the full article 2. Extract a 2-3 sentence synopsis covering:
- Policy or leadership development
- Impact on districts/schools
- Relevance to CIO/technology leadership
Freshness check: Verify article dates before including. Only include articles published within the last 24 hours. Skip older articles even if they appear in results.
Topics to cover:
- State and federal education policy
- Superintendent and board news
- School funding and budgets
- Workforce and staffing trends
1.9b School Safety & Security News (with Synopses)
Use WebSearch:
K-12 school safety security news past 24 hours January 2026For each article found (3 articles): 1. Use WebFetch to read the full article 2. Extract a 2-3 sentence synopsis covering:
- What happened / what's new
- Impact on school safety practices
- Key takeaway for districts
Freshness check: Verify article dates before including. Only include articles published within the last 24 hours. Skip older articles even if they appear in results.
Topics to cover:
- School security incidents and responses
- Safety policy changes
- Emergency preparedness
- Mental health and threat assessment
- Physical security technology
1.10 Technology Team EOD Messages (Last Business Day) - DETAILED
Get end-of-day check-in messages from the Technology Staff space:
cd /Users/hagelk/non-ic-code/geoffrey/skills/google-workspace && bun chat/get_eod_messages.js psd spaces/AAAAxOtpv10 last-business-dayCRITICAL - NO HALLUCINATION:
- ONLY use names that appear in the
senderfield of the script output - If script returns no messages, say "No EOD messages found"
- If script returns messages with sender IDs instead of names, display the ID (e.g., "users/12345...")
- NEVER invent names, locations, or accomplishments
- Copy-paste approach: treat script output as source of truth
Before extracting team data: 1. Examine the raw JSON output from the script 2. List the exact sender values returned 3. Use ONLY those names - no paraphrasing, no "improving"
CRITICAL: Extract FULL details from each team member's EOD message. Look for messages that contain "Today:" prefix - these are the detailed EOD summaries.
For each team member who posted an EOD summary: 1. Name: Who posted (use EXACTLY the name from sender field) 2. Location(s): Where they worked (WFH, DCRC, school sites) 3. Key accomplishments: Specific tasks completed (not just "tickets") 4. Notable items: Interesting problems solved, projects worked on 5. Issues/blockers: Any problems mentioned
Example extraction from raw message:
Brad White:
- Location: TSD (Tech Services)
- Accomplished: Packaged Cinema 4D plugin for deployment, fixed OAuth blocking for Maxon App sign-in, created SwiftDialog notification for plugin installs, used Claude Code for first time to create Installomator label for Godot game engine
- Notable: Working on Unity deployment troubleshooting, burning comp time leaving earlyNote: If today is Monday, "last business day" = Friday (or Thursday if Friday was a holiday).
1.10b Safety & Security Team EOD Messages (Last Business Day)
Get end-of-day check-in messages from the Safety & Security Staff space:
cd /Users/hagelk/non-ic-code/geoffrey/skills/google-workspace && bun chat/get_eod_messages.js psd spaces/AAAAFpQaAnA last-business-daySpace: PSD Safety & Security Team
CRITICAL - NO HALLUCINATION:
- ONLY use names that appear in the
senderfield of the script output - If script returns no messages, say "No EOD messages found"
- If script returns messages with sender IDs (e.g., "users/12345...") instead of names:
- Display as: Unknown (users/123456...) - [Location from message content]
- Add note: "Name not in mapping - update chat_user_mapping.json"
- NEVER invent names, locations, or accomplishments
- Copy-paste approach: treat script output as source of truth
Before extracting team data: 1. Examine the raw JSON output from the script 2. List the exact sender values returned 3. Use ONLY those names - no paraphrasing, no "improving"
Extract using same format as Technology Team - names, locations, accomplishments, issues.
Note: If today is Monday, "last business day" = Friday (or Thursday if Friday was a holiday).
1.11 Team Completed Tickets (Last Business Day)
Get tickets closed by the Technology team on the last business day:
# First get the last business day
LAST_BIZ_DAY=$(bun /Users/hagelk/non-ic-code/geoffrey/skills/morning-briefing/scripts/get_last_business_day.js | jq -r '.date')
# Then get daily summary for that date
bun /Users/hagelk/non-ic-code/geoffrey/skills/freshservice-manager/scripts/get_daily_summary.js "$LAST_BIZ_DAY"CRITICAL - NO HALLUCINATION:
- ONLY use agent names that appear in the
byAgentsection of script output - Use EXACTLY the ticket counts returned by the script
- NEVER invent names or ticket counts
- If an agent's name shows as ID or email, display it as-is
Before reporting ticket stats: 1. Examine the raw JSON output from get_daily_summary.js 2. List the exact agent names from byAgent field 3. Use ONLY those names and counts
Returns:
- Total tickets closed
- Breakdown by agent
- Breakdown by category (Password Reset, Chromebook, etc.)
- Automated vs agent-resolved
Workspace: Technology (workspace_id: 2)
1.12 Ticket Trends Analysis
Compare last business day to previous days for trends:
# Get last 5 business days of summaries for trend analysis
# The get_daily_summary.js script supports date parsing
bun /Users/hagelk/non-ic-code/geoffrey/skills/freshservice-manager/scripts/get_daily_summary.js "last monday"
bun /Users/hagelk/non-ic-code/geoffrey/skills/freshservice-manager/scripts/get_daily_summary.js "last tuesday"
# etc.Analyze for:
- Volume trends (increasing/decreasing)
- Category spikes (sudden increase in specific issue types)
- Agent workload distribution
- Unusual patterns
1.13 Software Development Workspace Tickets
Get open tickets in the Software Development workspace:
bun /Users/hagelk/non-ic-code/geoffrey/skills/freshservice-manager/scripts/search_tickets.js "status:2 OR status:3" 13Returns all open (status:2) and pending (status:3) tickets in the Software Development workspace.
Workspace ID: 13 (Software Development) Note: This is the user's internal software development bug tracker for AI Studio and other PSD applications.
1.14 Legislative Activity (Last Business Day)
Get K-12 education bills with activity since the last business day:
# Get lookback info and bills to check
bun /Users/hagelk/non-ic-code/geoffrey/skills/legislative-tracker/scripts/get_recent_bill_activity.js --last-business-dayThis returns:
- Date range to check (last business day → today)
- List of ~143 confirmed education bill IDs
- WebFetch instructions for each bill
Workflow: 1. Get the output from get_recent_bill_activity.js 2. WebFetch each bill URL (batch 5-6 in parallel for speed) 3. Extract latest action date from each bill page 4. Filter to bills where latest_action_date >= lookback_start 5. Apply priority framework (HIGH/MEDIUM/LOW based on district impact) 6. Include summary of what each bill does
On Monday: Lookback starts on Friday (or earlier if Friday was a holiday), so includes all weekend activity (hearings, votes, committee actions).
Example URLs to WebFetch:
https://app.leg.wa.gov/billsummary?BillNumber=1020&Year=2025https://app.leg.wa.gov/billsummary?BillNumber=5038&Year=2025
Note: Only include bills that actually had activity. If no bills moved, output "No legislative activity since [date]".
Phase 2: Generate Briefing
2.1 Analyze & Prioritize
Review gathered data and identify:
- Conflicts: Overlapping calendar events
- Urgencies: Overdue tasks, high-priority tickets
- Themes: Patterns across data sources
2.2 Format Markdown Briefing
Use this structure:
# Daily Briefing - [DATE]
## Weather
[Current conditions, high/low, precipitation]
## Today's Calendar
| Time | Event | Location |
|------|-------|----------|
| ... | ... | ... |
**Conflicts/Notes**: [any issues]
## Priority Tasks
### Due Today
- [ ] Task 1
- [ ] Task 2
### Flagged
- [ ] Task 3
### Overdue
- [ ] Task 4 (due [date])
## Recent Emails (Last 24h)
[X emails in inbox from last 24 hours]
### Needs Response
- From: [sender] - [subject] (snippet)
- From: [sender] - [subject] (snippet)
### FYI/Notifications
- [sender] - [subject]
## Freshservice
### Technology Tickets: [count] open
[Top 3-5 tickets by priority/age]
### Software Development Tickets: [count] open
[List tickets in Software Dev workspace]
### Pending Approvals: [count]
[List with ticket #, requester, summary]
## Team Activity (Last Business Day: [DAY, DATE])
**Data Source Verification (REQUIRED):**
- Technology EOD script returned: [X] messages from [list exact sender names from JSON]
- Safety & Security EOD script returned: [X] messages from [list exact sender names from JSON]
- Freshservice daily summary returned: [X] tickets by [list exact agent names from JSON]
*Use ONLY the names listed above. Never invent or paraphrase names.*
### Technology Team EOD Summaries
**[Name from sender field]** - [Location(s)]
- [Key accomplishment 1 - be specific about what they did]
- [Key accomplishment 2]
- [Notable: any interesting problems solved or projects]
**[Name from sender field]** - [Location(s)]
- [Key accomplishment 1]
- [Key accomplishment 2]
- [Issues: any blockers or problems mentioned]
*[Continue for each team member who posted an EOD summary]*
### Safety & Security Team EOD Summaries
**[Name from sender field]** - [Location(s)]
- [Key accomplishment 1]
- [Key accomplishment 2]
- [Notable: any issues or incidents handled]
*If sender shows as user ID (users/12345...), display: **Unknown (users/12345...)** - [Location]*
*[Continue for each team member who posted an EOD summary]*
### Tickets Completed by Team: [count]
| Agent | Tickets | Top Categories |
|-------|---------|----------------|
| [Name from byAgent field] | X | Password Reset (Y), Chromebook (Z) |
### Ticket Trends
- Volume: [up/down/stable] vs previous days
- Notable patterns: [any spikes or anomalies]
## EdTech News
### [Article Title 1] - [Source]
[2-3 sentence synopsis: what happened, why it matters, key takeaway]
### [Article Title 2] - [Source]
[2-3 sentence synopsis]
### [Article Title 3] - [Source]
[2-3 sentence synopsis]
## AI News
### [Article Title 1] - [Source]
[2-3 sentence synopsis: what's the development, industry impact, relevance]
### [Article Title 2] - [Source]
[2-3 sentence synopsis]
### [Article Title 3] - [Source]
[2-3 sentence synopsis]
## K-12 Leadership News
### [Article Title 1] - [Source]
[2-3 sentence synopsis: policy/leadership development, impact, relevance to tech leadership]
### [Article Title 2] - [Source]
[2-3 sentence synopsis]
## School Safety & Security News
### [Article Title 1] - [Source]
[2-3 sentence synopsis: what happened, impact on safety practices, takeaway for districts]
### [Article Title 2] - [Source]
[2-3 sentence synopsis]
### [Article Title 3] - [Source]
[2-3 sentence synopsis]
## Legislative Activity ([Last Biz Day] - Today)
[X] education bills had movement:
### 🔴 HIGH Priority
#### [Bill ID] - [Short Title]
**Action**: [What happened - hearing, vote, committee action, etc.]
**Summary**: [1-2 sentences: what the bill does, potential district impact]
### 🟡 MEDIUM Priority
#### [Bill ID] - [Short Title]
**Action**: [What happened]
**Summary**: [1-2 sentences]
### 🟢 LOW Priority
- [Bill ID]: [Action type] - [One line summary]
*No legislative activity since [date]* - if no bills moved
## Quick Stats
- Calendar events: X
- Tasks overdue: X | Due today: X | Flagged: X
- Open tickets (Tech): X
- Open tickets (Software Dev): X
- Pending approvals: X
- Recent emails (24h): X
- Team tickets closed [last biz day]: X
- News articles: EdTech (X) | AI (X) | Leadership (X)
- Legislative bills with activity: X2.3 Generate Podcast Script (Extended Format)
Transform the briefing into a comprehensive conversational audio script:
- First person, casual professional tone
- 10-15 minutes speaking time (~1500-2000 words)
- Address listener directly ("Here's what you need to know today...")
Required Sections (in order):
1. Opening (~100 words)
- Day, date, weather summary
- Quick preview of key items
2. Calendar & Schedule (~150 words)
- Today's events with context
- Highlight key meetings/events
- Note any conflicts or prep needed
3. Tasks & Priorities (~150 words)
- Overdue items that need attention
- Due today items
- Flagged priorities
4. Tickets & Service Desk (~150 words)
- Open Technology tickets
- Software Development tickets
- Pending approvals
5. Team Activity - DETAILED (~400 words)
- What the Technology team accomplished on last business day
- Highlight 3-5 team members by name with specifics
- Notable projects, interesting problems solved
- What the Safety & Security team accomplished
- Overall ticket closure stats
CRITICAL - NO HALLUCINATION:
- Use ONLY names from the script output
senderfields - Use ONLY ticket counts from the
get_daily_summary.jsoutput - If script returned no messages, say "No EOD messages were posted"
- NEVER invent names, locations, or accomplishments
6. EdTech News (~200 words)
- 2-3 articles with synopses
- Why each matters for K-12
7. AI News (~200 words)
- 2-3 articles with synopses
- Industry impact and relevance
8. K-12 Leadership News (~150 words)
- 1-2 articles with synopses
- Policy/leadership implications
8b. School Safety & Security News (~150 words)
- 2-3 articles with synopses
- Safety practices and district implications
9. Legislative Update (~150 words)
- Bills that had hearings, votes, or readings since last business day
- Highlight any with direct district impact (fiscal, operational, staffing)
- Note upcoming hearing dates if relevant
- On Mondays, include weekend activity summary
10. Closing (~100 words)
- Top 3 priorities for the day
- Sign off
Style Guidelines:
- Use team members' first names when discussing their work
- Include specific details (not "worked on tickets" but "resolved 34 Chromebook repairs")
- Transition smoothly between sections
- Add brief commentary/analysis on news items
Save to /tmp/morning_briefing_podcast.txt
Phase 3: Create Podcast
uv run --with mlx-audio --with pydub /Users/hagelk/non-ic-code/geoffrey/skills/local-tts/scripts/generate_audio.py \
--file /tmp/morning_briefing_podcast.txt \
--voice af_heart \
--output ~/Desktop/morning_briefing_[DATE].mp3Voice Selection: af_heart (warm, friendly - good for morning briefing) Note: Uses local MLX TTS (Kokoro model) - no API costs
Phase 3.5: Generate Infographic (MANDATORY)
CRITICAL: The infographic MUST be generated. Do not skip this phase.
Create a visual summary infographic using the image-gen skill.
3.5.1 Build Infographic Prompt
Based on gathered data, construct a prompt for the infographic:
Create an infographic summarizing a daily work briefing for [DATE].
Visual concept: A clean dashboard layout with distinct sections for different data categories.
Key data to display:
- Weather: [conditions], High [X]°F, Low [Y]°F
- Calendar: [X] events today, highlight: [key meeting]
- Tasks: [X] overdue, [Y] due today, [Z] flagged
- Tickets: [X] Technology open, [Y] Software Dev open
- Team Activity: [X] tickets closed by team yesterday
- Emails: [X] in inbox from last 24h
Style: Professional, clean design with PSD brand colors (navy blue #003366, gold accent #FFD700).
Flat design, clear sections, modern sans-serif typography.
Layout: Horizontal 16:9, organized as a dashboard with weather top-left, calendar top-right,
tasks and tickets in middle row, team stats at bottom.
Title: "Daily Briefing - [DATE]"
Subtitle: "Gig Harbor, WA"3.5.2 Generate Image
uv run /Users/hagelk/non-ic-code/geoffrey/skills/image-gen/scripts/generate.py \
"[infographic prompt]" \
~/Desktop/morning_briefing_[DATE].png \
16:9 \
2KSettings:
- Aspect ratio: 16:9 (landscape dashboard)
- Size: 2K (ensures text readability)
- Output:
~/Desktop/morning_briefing_[DATE].png
Phase 4: Save to Obsidian
4.1 Copy Infographic to Obsidian Assets
cp ~/Desktop/morning_briefing_[DATE].png \
~/Library/Mobile\ Documents/iCloud~md~obsidian/Documents/Personal_Notes/Geoffrey/Daily\ Briefings/assets/4.2 Add Infographic to Briefing Markdown
At the top of the briefing (after the title), add:
4.3 Save Briefing File
Use Obsidian MCP tools:
1. Check if daily note exists:
mcp__obsidian-vault__get_vault_file
filename: "Geoffrey/Daily Briefings/[YYYY-MM-DD].md"2. Create or update:
mcp__obsidian-vault__create_vault_file
filename: "Geoffrey/Daily Briefings/[YYYY-MM-DD].md"
content: [full briefing markdown with infographic embed]File path pattern: Geoffrey/Daily Briefings/YYYY-MM-DD.md
Fallback (if MCP unavailable): Write directly to iCloud path:
/Users/hagelk/Library/Mobile Documents/iCloud~md~obsidian/Documents/Personal_Notes/Geoffrey/Daily Briefings/[YYYY-MM-DD].mdPhase 5: Send Email (HTML with Inline Infographic)
5.1 Generate HTML Email Body
Create a professional HTML email (NOT markdown). Use the template at skills/morning-briefing/templates/email.html as reference.
Key HTML structure:
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 600px; margin: 0 auto; }
.header { text-align: center; border-bottom: 2px solid #003366; padding: 16px; }
.stat-box { background: #f8f9fa; padding: 12px; text-align: center; }
/* ... more styles from template ... */
</style>
</head>
<body>
<!-- Infographic at TOP - referenced via cid:briefing_image -->
<img src="cid:briefing_image" alt="Daily Briefing" style="width: 100%; max-width: 600px; border-radius: 8px; margin-bottom: 20px;">
<div class="header">
<h1 style="color: #003366;">Daily Briefing</h1>
<p>[DAY_OF_WEEK], [DATE_FORMATTED]</p>
</div>
<!-- Weather, Stats, Calendar, Tasks, Team, News sections -->
<!-- See templates/email.html for full structure -->
<div style="background: #e8f4f8; padding: 16px; border-radius: 8px; margin-top: 20px;">
<strong>Attachments:</strong><br>
🎧 Audio Podcast (~10-15 min)<br>
📊 Infographic (also shown above)
</div>
<p style="text-align: center; color: #666; font-size: 12px;">
Full briefing: Obsidian/Geoffrey/Daily Briefings/[DATE].md
</p>
</body>
</html>CRITICAL: The <img src="cid:briefing_image"> displays the infographic inline at the top of the email.
Save to /tmp/morning_briefing_email.html
5.2 Send with Inline Image + Attachments
cd /Users/hagelk/non-ic-code/geoffrey/skills/google-workspace && bun gmail/send_with_attachments.js psd \
--to "hagelk@psd401.net" \
--subject "Daily Briefing - [DATE]" \
--body-file /tmp/morning_briefing_email.html \
--html \
--inline-image "~/Desktop/morning_briefing_[DATE].png" \
--attachments "~/Desktop/morning_briefing_[DATE].mp3"Options explained:
--html- Treat body as HTML (required for formatting)--inline-image- Embeds image at top of email (referenced ascid:briefing_image)--attachments- Additional files to attach (podcast)
Result:
- Infographic displays INLINE at top of email (visible without downloading)
- Podcast attached for download
- Email renders nicely in Gmail with professional styling
Output
Return standardized summary:
## Summary
Generated daily briefing for [DATE]
## Actions
- Gathered calendar events: X
- Gathered tasks: X (Y due today, Z flagged)
- Gathered Technology tickets: X open, Y approvals pending
- Gathered Software Dev tickets: X open
- Gathered team EOD messages: X from [last business day]
- Gathered team completed tickets: X from [last business day]
- Weather: [conditions]
- News: X headlines
- Generated podcast: ~/Desktop/morning_briefing_[DATE].mp3
- Saved to Obsidian: Geoffrey/Daily Briefings/[DATE].md
- Email: [sent/skipped - reason]
## Status
[Status emoji] [Complete/Partial]
## Quick View
[2-3 line summary of most important items]Error Handling
Missing Google Workspace Scripts
If calendar/email scripts don't exist:
- Skip those sections
- Note "Integration pending" in output
- Continue with available data sources
OmniFocus Not Running
Status: Partial
Note: OmniFocus not running - task data unavailableFreshservice API Error
Status: Partial
Note: Freshservice API error - ticket data unavailableLocal TTS Generation Failed
Status: Partial
Note: Audio generation failed - check mlx-audio setup
Briefing saved to Obsidian without podcastDependencies
| Skill/Tool | Required For | Fallback |
|---|---|---|
| google-workspace | Calendar, email, team EOD | Skip sections |
| omnifocus-manager | Tasks | Skip section |
| freshservice-manager | Tickets, approvals, team stats | Skip section |
| local-tts | Audio podcast | Text-only briefing |
| obsidian-vault (MCP) | Save briefing | Display only |
| WebSearch | News, weather | Skip sections |
Configuration
User Preferences
- Location: Gig Harbor, WA (weather)
- Work Email: psd account
- Technology Workspace: workspace_id: 2
- Software Dev Workspace: workspace_id: 13
- Technology Chat Space: spaces/AAAAxOtpv10 (Technology Staff Check-Ins & Logs)
- Safety & Security Chat Space: spaces/AAAAFpQaAnA (PSD Safety & Security Team)
- Voice: af_heart (warm, friendly - local Kokoro TTS)
- Podcast Length: 10-15 minutes (~1500-2000 words)
Customization
Users can request:
- "Skip news" - omit news section
- "No podcast" - text only
- "Just calendar and tasks" - minimal briefing
{
"description": "Name pronunciations for TTS. Keys are the original spelling, values are phonetic spellings.",
"names": {
"Jaden": "Jayden",
"Hans": "Hahns",
"Loesch": "Lowsh",
"Bahr": "Bar",
"Winget": "Wingit"
},
"pending": {
"_comment": "Names that need pronunciation confirmation"
}
}
Podcast Script Template (Extended Format)
Guidelines
Target Length: 1500-2000 words (~10-15 minutes spoken)
Tone: Casual professional, first person, warm but substantive. Like a trusted colleague giving you a thorough briefing over coffee.
Key Principles:
- Use team members' first names
- Include specific details, not generalizations
- Provide context and analysis on news items
- Smooth transitions between sections
Structure
| Section | Words | Time |
|---|---|---|
| Opening | ~100 | 45 sec |
| Calendar & Schedule | ~150 | 1 min |
| Tasks & Priorities | ~150 | 1 min |
| Tickets & Service Desk | ~150 | 1 min |
| Team Activity (DETAILED) | ~400 | 3 min |
| EdTech News | ~200 | 1.5 min |
| AI News | ~200 | 1.5 min |
| K-12 Leadership News | ~150 | 1 min |
| Closing | ~100 | 45 sec |
| Total | ~1600 | ~12 min |
Template
Good morning. It's [WEEKDAY], [MONTH DAY spelled out].
[WEATHER] - [Conditions] today with a high of [X]. [Any notable weather coming].
Here's your comprehensive briefing for the day.
---
CALENDAR AND SCHEDULE
[You've got X events on the calendar today / Today's calendar is [light/moderate/packed]].
[For each significant event]:
- [Time]: [Event name]. [Brief context - who's there, what it's about, any prep needed].
[Note any conflicts, back-to-back situations, or key transitions needed].
---
TASKS AND PRIORITIES
Let's talk about what needs your attention.
[If overdue - be specific]:
You've got [X] overdue items. The most pressing: [Item 1 name] is now [X] days overdue - [brief context on why it matters]. Also overdue: [Item 2], [Item 3 if notable].
[Due today]:
For today specifically, [X] tasks are due. [Describe briefly or note "nothing due today" if applicable].
[Flagged]:
You've flagged [X] items for focus. [Mention any that deserve attention].
---
TICKETS AND SERVICE DESK
On Freshservice, here's where things stand.
Technology workspace: [X] open tickets. [Note any high priority or assigned to you].
Software Development workspace: [X] open tickets. [Brief note on top issues if relevant].
Pending approvals: [X] waiting for your sign-off. [List briefly if important].
---
TEAM ACTIVITY FROM [DAY]
Let's go through what the team accomplished on [last business day].
[For each team member with a detailed EOD - use first names]:
[Name] was at [location(s)]. [He/She] [accomplished X, Y, Z - be specific about tasks].
[Include any notable projects, interesting problems solved, or issues mentioned].
[Continue for 4-6 team members]
Overall, the team closed [X] tickets on [day]. The biggest categories were [Category 1] at [X] tickets and [Category 2] at [X] tickets. [Any trend observations].
---
EDTECH NEWS
A few stories worth knowing about in education technology.
First, [Article title/topic]. [2-3 sentences: what happened, why it matters for K-12, key takeaway].
Second, [Article title/topic]. [2-3 sentences synopsis].
[Third if notable]: Also worth noting, [Article topic]. [1-2 sentence synopsis].
---
AI NEWS
In the broader AI world, here's what's happening.
[Article 1 topic]. [2-3 sentences: what's the development, industry impact, how it connects to your work].
[Article 2 topic]. [2-3 sentences synopsis].
[Article 3 if notable]: [1-2 sentence mention].
---
K-12 LEADERSHIP NEWS
On the leadership and policy front:
[Article 1 topic]. [2-3 sentences: what's the policy or leadership development, impact on districts, relevance to tech leadership role].
[Article 2 if notable]: [1-2 sentence mention].
---
CLOSING
So to wrap up, your top priorities today:
First, [Priority 1 - most important meeting/task].
Second, [Priority 2].
Third, [Priority 3].
That's your briefing for [WEEKDAY], [MONTH DAY]. Have a productive day.Example (Abbreviated)
Good morning. It's Monday, January twenty-sixth.
Expect partly sunny skies today with a high of forty-seven. Rain's coming Tuesday night, so enjoy the dry weather while it lasts.
Here's your comprehensive briefing for the day.
CALENDAR AND SCHEDULE
You've got six events on the calendar today, including two significant ones.
At seven AM, you've got the dentist - that's a one hour appointment.
At noon, the RTM AI Panel prep call with Priya, Rachel, Marc, and Cody. Make sure you've reviewed that prep doc before the call.
Then it gets busy in the evening. At four, you've got the levy info sharing at KPMS, followed immediately by the community presentation at Evergreen Elementary at six. Back-to-back levy events, so pace yourself.
TASKS AND PRIORITIES
Let's talk about what needs your attention.
You've got eighteen overdue items. The most pressing: The ACPE session proposal is now eleven days overdue - that deadline has passed and needs immediate action. The EdTech Advocacy Day travel approval is six days overdue along with several others including the Ashley meeting about Scholarship Granting Organizations.
Nothing specifically due today, but that overdue pile needs attention.
TICKETS AND SERVICE DESK
On Freshservice, Technology workspace has three open tickets including two digital resource approvals assigned to you - Math Playground and Paw Patrol.
Software Development workspace has ten open tickets, mostly bug reports from AI Studio users.
One pending approval waiting for your sign-off.
TEAM ACTIVITY FROM FRIDAY
Let's go through what the team accomplished on Friday.
Brad was at Tech Services. He spent the day packaging Cinema 4D plugins for deployment, fixed an OAuth blocking issue for the Maxon App, and - this is interesting - used Claude Code for the first time to create an Installomator label for Godot, an open-source game engine requested for a Mac Lab at GHH. He also worked on Unity deployment troubleshooting and left early to burn some comp time.
Jodi was at DCRC. She helped an employee set up a new MacBook, supervised interns, processed invoices, and spent time helping Teresa fix a mistake in Destiny - apparently there were a lot of steps involved but she figured it all out.
David was at DCRC and then TSD. He built a new Securly block for a student, replaced a printer in ESS, and noted a trend - once people update to the newest OS, Screen Share stops working with Prometheans until the app is updated via Self Service.
[Continue for other team members...]
Overall, the team closed one hundred twenty-seven tickets on Friday. The biggest categories were Chromebook repairs at thirty-four tickets and password resets at eleven.
EDTECH NEWS
A few stories worth knowing about in education technology.
First, states are putting unprecedented attention on AI's role in schools. Several states are now requiring districts to develop AI policies, with Ohio mandating adoption by July first this year. This is moving from optional guidance to compliance requirements.
Second, EdSurge is tracking K-12 education trends for twenty twenty-six, with AI integration and digital equity remaining top priorities for technology leaders.
[Continue with AI News and Leadership News...]
CLOSING
So to wrap up, your top priorities today:
First, nail that RTM panel prep call at noon - review the prep doc beforehand.
Second, bring your A-game for the back-to-back levy presentations this evening.
Third, tackle that overdue ACPE session proposal when you get a chance.
That's your briefing for Monday, January twenty-sixth. Have a productive day.Voice Notes for ElevenLabs
- Use "Rachel" voice (calm, clear, professional)
- Numbers: Spell out one through ten, use words for larger numbers in speech context
- Dates: "January twenty-sixth" not "January 26"
- Contractions are fine: "you've got", "it's", "don't"
- Use first names for team members
- Pause indicators: Use "---" between major sections (will create natural pauses)
- Avoid: Ticket numbers, technical jargon without context, acronyms on first use
#!/usr/bin/env bun
/**
* Apply pronunciation substitutions to podcast text
* Usage: bun apply_pronunciations.js < input.txt > output.txt
* bun apply_pronunciations.js --file /path/to/podcast.txt
*/
import { readFileSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const guideFile = join(__dirname, '..', 'pronunciation_guide.json');
function loadPronunciations() {
try {
const guide = JSON.parse(readFileSync(guideFile, 'utf-8'));
return guide.names || {};
} catch (e) {
console.error(`Warning: Could not load pronunciation guide: ${e.message}`);
return {};
}
}
function applyPronunciations(text, pronunciations) {
let result = text;
for (const [original, phonetic] of Object.entries(pronunciations)) {
// Replace whole word matches (case-insensitive, preserve case of first letter)
const regex = new RegExp(`\\b${original}\\b`, 'gi');
result = result.replace(regex, (match) => {
// Preserve capitalization
if (match[0] === match[0].toUpperCase()) {
return phonetic.charAt(0).toUpperCase() + phonetic.slice(1);
}
return phonetic.toLowerCase();
});
}
return result;
}
// Main
const pronunciations = loadPronunciations();
const args = process.argv.slice(2);
if (args.includes('--file')) {
const fileIndex = args.indexOf('--file') + 1;
const filePath = args[fileIndex];
if (!filePath) {
console.error('Error: --file requires a path argument');
process.exit(1);
}
const text = readFileSync(filePath, 'utf-8');
const result = applyPronunciations(text, pronunciations);
writeFileSync(filePath, result);
console.error(`Applied ${Object.keys(pronunciations).length} pronunciation rules to ${filePath}`);
} else if (args.includes('--help') || args.includes('-h')) {
console.log(`
Apply pronunciation substitutions to podcast text.
Usage:
bun apply_pronunciations.js --file /path/to/podcast.txt # Modify file in-place
cat podcast.txt | bun apply_pronunciations.js # Pipe mode
Pronunciation guide: ${guideFile}
`);
} else {
// Pipe mode - read from stdin
const text = readFileSync(0, 'utf-8'); // 0 = stdin
const result = applyPronunciations(text, pronunciations);
process.stdout.write(result);
}
#!/usr/bin/env osascript -l JavaScript
// Get tasks that are due today, overdue, or flagged
// Usage: osascript -l JavaScript get_due_flagged.js
//
// Returns tasks that are:
// - Due today or in the past (overdue)
// - Flagged
// - Available (not blocked, not completed, not dropped, not deferred to future)
function run(argv) {
var app = Application("OmniFocus");
// Check if OmniFocus is running
if (!app.running()) {
return JSON.stringify({
error: "OmniFocus is not running",
suggestion: "Please open OmniFocus and try again"
}, null, 2);
}
var doc = app.defaultDocument;
var now = new Date();
var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
var tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
// Get all tasks from the document
var allTasks = doc.flattenedTasks();
var dueToday = [];
var overdue = [];
var flagged = [];
for (var i = 0; i < allTasks.length; i++) {
var t = allTasks[i];
// Skip completed or dropped tasks
if (t.completed() || t.dropped()) continue;
// Skip tasks deferred to the future
var deferDate = t.deferDate();
if (deferDate && deferDate > now) continue;
// Skip tasks in on-hold or dropped projects
var container = t.containingProject();
if (container) {
var status = container.status();
if (status === "on hold" || status === "dropped") continue;
}
var dueDate = t.dueDate();
var isFlagged = t.flagged();
var projectName = container ? container.name() : "No Project";
var taskData = {
id: t.id(),
name: t.name(),
project: projectName,
dueDate: dueDate ? dueDate.toISOString().split("T")[0] : null,
flagged: isFlagged,
tags: t.tags().map(function(tag) { return tag.name(); }),
note: t.note() ? t.note().substring(0, 100) : ""
};
// Categorize by due date
if (dueDate) {
if (dueDate < today) {
taskData.status = "overdue";
taskData.daysOverdue = Math.floor((today - dueDate) / (24 * 60 * 60 * 1000));
overdue.push(taskData);
} else if (dueDate < tomorrow) {
taskData.status = "due_today";
dueToday.push(taskData);
}
}
// Track flagged separately (may overlap with due)
if (isFlagged && !dueDate) {
// Only add to flagged if not already in due categories
taskData.status = "flagged";
flagged.push(taskData);
} else if (isFlagged && dueDate && dueDate >= tomorrow) {
// Flagged but due in future
taskData.status = "flagged_future";
flagged.push(taskData);
}
}
// Sort overdue by days overdue (most overdue first)
overdue.sort(function(a, b) { return b.daysOverdue - a.daysOverdue; });
// Sort due today by project
dueToday.sort(function(a, b) { return a.project.localeCompare(b.project); });
// Sort flagged by project
flagged.sort(function(a, b) { return a.project.localeCompare(b.project); });
return JSON.stringify({
generated: now.toISOString(),
summary: {
overdue: overdue.length,
dueToday: dueToday.length,
flagged: flagged.length,
total: overdue.length + dueToday.length + flagged.length
},
overdue: overdue,
dueToday: dueToday,
flagged: flagged
}, null, 2);
}
#!/usr/bin/env bun
/**
* Get Last Business Day
*
* Calculates the last business day before the current date,
* considering weekends and US federal holidays.
*
* Usage: bun get_last_business_day.js [reference_date]
*
* Returns: JSON with last business day date and info
*/
// US Federal Holidays (observed dates for 2025-2026)
const FEDERAL_HOLIDAYS_2025 = [
'2025-01-01', // New Year's Day
'2025-01-20', // MLK Jr. Day
'2025-02-17', // Presidents' Day
'2025-05-26', // Memorial Day
'2025-06-19', // Juneteenth
'2025-07-04', // Independence Day
'2025-09-01', // Labor Day
'2025-10-13', // Columbus Day
'2025-11-11', // Veterans Day
'2025-11-27', // Thanksgiving
'2025-12-25', // Christmas
];
const FEDERAL_HOLIDAYS_2026 = [
'2026-01-01', // New Year's Day
'2026-01-19', // MLK Jr. Day
'2026-02-16', // Presidents' Day
'2026-05-25', // Memorial Day
'2026-06-19', // Juneteenth
'2026-07-03', // Independence Day (observed)
'2026-09-07', // Labor Day
'2026-10-12', // Columbus Day
'2026-11-11', // Veterans Day
'2026-11-26', // Thanksgiving
'2026-12-25', // Christmas
];
const ALL_HOLIDAYS = new Set([...FEDERAL_HOLIDAYS_2025, ...FEDERAL_HOLIDAYS_2026]);
/**
* Format date as YYYY-MM-DD in LOCAL time (not UTC)
*/
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* Parse date string as LOCAL time (not UTC)
* Accepts: "2026-01-27" or Date object
*/
function parseLocalDate(input) {
if (input instanceof Date) {
return input;
}
// Parse YYYY-MM-DD as local time by splitting components
const [year, month, day] = input.split('-').map(Number);
return new Date(year, month - 1, day); // month is 0-indexed
}
function isWeekend(date) {
const day = date.getDay();
return day === 0 || day === 6; // Sunday = 0, Saturday = 6
}
function isHoliday(date) {
return ALL_HOLIDAYS.has(formatDate(date));
}
function isBusinessDay(date) {
return !isWeekend(date) && !isHoliday(date);
}
function getLastBusinessDay(referenceDate = new Date()) {
const ref = referenceDate instanceof Date ? new Date(referenceDate) : parseLocalDate(referenceDate);
ref.setHours(0, 0, 0, 0);
// Start from the day before reference
const candidate = new Date(ref);
candidate.setDate(candidate.getDate() - 1);
// Keep going back until we find a business day
let daysBack = 1;
while (!isBusinessDay(candidate) && daysBack < 30) {
candidate.setDate(candidate.getDate() - 1);
daysBack++;
}
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
return {
date: formatDate(candidate),
dayOfWeek: dayNames[candidate.getDay()],
daysAgo: daysBack,
isHoliday: ALL_HOLIDAYS.has(formatDate(candidate)),
skipped: {
weekendDays: daysBack > 1 ? daysBack - 1 : 0,
holidays: daysBack > 1
},
referenceDate: formatDate(ref)
};
}
// CLI
const referenceArg = process.argv[2];
const referenceDate = referenceArg ? parseLocalDate(referenceArg) : new Date();
const result = getLastBusinessDay(referenceDate);
console.log(JSON.stringify(result, null, 2));
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Daily Briefing - {{DATE}}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: #ffffff;
border-radius: 8px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.header {
text-align: center;
border-bottom: 2px solid #003366;
padding-bottom: 16px;
margin-bottom: 20px;
}
.header h1 {
color: #003366;
margin: 0 0 8px 0;
font-size: 24px;
}
.header .date {
color: #666;
font-size: 14px;
}
.weather {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 16px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
}
.weather .temp {
font-size: 32px;
font-weight: bold;
}
.weather .conditions {
font-size: 14px;
opacity: 0.9;
}
.section {
margin-bottom: 20px;
}
.section h2 {
color: #003366;
font-size: 16px;
margin: 0 0 12px 0;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
}
.stat-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.stat-box {
background: #f8f9fa;
border-radius: 6px;
padding: 12px;
text-align: center;
}
.stat-box .number {
font-size: 24px;
font-weight: bold;
color: #003366;
}
.stat-box .label {
font-size: 12px;
color: #666;
text-transform: uppercase;
}
.highlight {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 12px;
margin: 12px 0;
border-radius: 0 6px 6px 0;
}
.calendar-item {
display: flex;
align-items: flex-start;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.calendar-item:last-child {
border-bottom: none;
}
.calendar-time {
background: #003366;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
margin-right: 12px;
white-space: nowrap;
}
.calendar-title {
flex: 1;
}
.priority-high {
color: #dc3545;
font-weight: bold;
}
.priority-medium {
color: #fd7e14;
}
.priority-low {
color: #28a745;
}
.news-item {
margin-bottom: 12px;
}
.news-item .source {
font-size: 11px;
color: #666;
text-transform: uppercase;
}
.news-item .title {
font-weight: 600;
color: #003366;
}
.news-item .synopsis {
font-size: 14px;
color: #555;
}
.footer {
text-align: center;
padding-top: 20px;
border-top: 1px solid #eee;
margin-top: 20px;
color: #666;
font-size: 12px;
}
.attachments {
background: #e8f4f8;
padding: 16px;
border-radius: 8px;
margin-top: 20px;
}
.attachments h3 {
margin: 0 0 12px 0;
color: #003366;
font-size: 14px;
}
.attachment-item {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.attachment-item:last-child {
margin-bottom: 0;
}
.attachment-icon {
margin-right: 8px;
font-size: 16px;
}
</style>
</head>
<body>
<div class="container">
<!-- Infographic at TOP - embedded via cid:briefing_image -->
<img src="cid:briefing_image" alt="Daily Briefing Infographic" style="width: 100%; max-width: 600px; border-radius: 8px; margin-bottom: 20px; display: block;">
<div class="header">
<h1>Daily Briefing</h1>
<div class="date">{{DAY_OF_WEEK}}, {{DATE_FORMATTED}}</div>
</div>
<div class="weather">
<div class="temp">{{WEATHER_TEMP}}</div>
<div class="conditions">{{WEATHER_CONDITIONS}}</div>
</div>
<div class="section">
<h2>Today at a Glance</h2>
<div class="stat-grid">
<div class="stat-box">
<div class="number">{{CALENDAR_COUNT}}</div>
<div class="label">Meetings</div>
</div>
<div class="stat-box">
<div class="number">{{TASKS_DUE}}</div>
<div class="label">Tasks Due</div>
</div>
<div class="stat-box">
<div class="number">{{TICKETS_OPEN}}</div>
<div class="label">Open Tickets</div>
</div>
<div class="stat-box">
<div class="number">{{EMAILS_COUNT}}</div>
<div class="label">New Emails</div>
</div>
</div>
</div>
{{#IF_OVERDUE}}
<div class="highlight">
<strong>Attention:</strong> {{OVERDUE_COUNT}} overdue task(s) need attention
</div>
{{/IF_OVERDUE}}
<div class="section">
<h2>Today's Calendar</h2>
{{CALENDAR_ITEMS}}
</div>
{{#IF_PRIORITIES}}
<div class="section">
<h2>Priority Tasks</h2>
{{PRIORITY_TASKS}}
</div>
{{/IF_PRIORITIES}}
<div class="section">
<h2>Team Activity ({{LAST_BIZ_DAY}})</h2>
<p>{{TEAM_SUMMARY}}</p>
</div>
<div class="section">
<h2>News Highlights</h2>
{{NEWS_ITEMS}}
</div>
{{#IF_LEGISLATIVE}}
<div class="section">
<h2>Legislative Activity</h2>
{{LEGISLATIVE_SUMMARY}}
</div>
{{/IF_LEGISLATIVE}}
<div class="attachments">
<h3>Attachments</h3>
<div class="attachment-item">
<span class="attachment-icon">🎧</span>
<span>Audio Podcast (~10-15 min)</span>
</div>
<div class="attachment-item">
<span class="attachment-icon">📊</span>
<span>Infographic Summary</span>
</div>
</div>
<div class="footer">
<p>Full briefing saved to Obsidian: Geoffrey/Daily Briefings/{{DATE}}.md</p>
<p>Generated by Geoffrey</p>
</div>
</div>
</body>
</html>