Use when extracting Microsoft Teams chat messages - navigates Teams, captures clipboard, parses to JSON, and persists with deterministic new message detection
From teams-scrapenpx claudepluginhub nathanvale/side-quest-marketplace-old --plugin teams-scrapeThis skill uses the workspace's default tool permissions.
FORMAT.mdDesigns and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Implements structured self-debugging workflow for AI agent failures: capture errors, diagnose patterns like loops or context overflow, apply contained recoveries, and generate introspection reports.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Extract chat messages from Microsoft Teams desktop app using macos-automator MCP tools and persist with deterministic new message detection.
Invoke this skill when you need to:
execute_script tool)This skill uses a two-phase approach:
The CLI handles parsing/storage/diffing, while AppleScript handles UI automation.
Scrape results are saved to ~/.config/teams-scrape/ for tracking changes between runs.
~/.config/teams-scrape/
├── ben-laughlin.json # Kebab-case filename from target name
├── jay-pancholi.json
└── engineering-standup.json
interface StoredChat {
target: string; // Original target name
targetSlug: string; // Kebab-case filename
lastScrapedAt: string; // ISO 8601 timestamp of last scrape
messageCount: number; // Total messages stored
messages: TeamsMessage[]; // All captured messages (sorted by timestamp)
}
interface TeamsMessage {
id: string; // Stable hash ID for deduplication
author: string;
timestamp: string; // ISO 8601
content: string;
replyTo?: { author: string; preview: string };
reactions?: { emoji: string; count: number; name: string }[];
attachments?: { type: "gif" | "image" | "file" | "url" | "loop"; name?: string; url?: string }[];
mentions?: string[];
edited?: boolean;
}
The CLI uses stable message IDs (hash of author + timestamp + content prefix) to detect truly new messages:
TeamsMessage[] with stable IDsThis ensures running the same scrape twice reports "0 new messages" - the detection is deterministic.
The teams-scrape CLI provides three commands:
# Process clipboard (main command)
pbpaste | bun run plugins/teams-scrape/src/cli.ts process "Ben Laughlin"
# Load existing history (read-only)
bun run plugins/teams-scrape/src/cli.ts load "Ben Laughlin"
# List all stored chats
bun run plugins/teams-scrape/src/cli.ts list
{
"target": "Ben Laughlin",
"capturedAt": "2026-01-20T10:00:00.000Z",
"isNewScrape": false,
"totalMessages": 31,
"newMessages": [
{
"id": "abc123def456",
"author": "Ben Laughlin",
"timestamp": "2026-01-20T09:15:00.000Z",
"content": "Hey, did you see the PR?"
}
],
"storagePath": "/Users/nathan/.config/teams-scrape/ben-laughlin.json"
}
Use macos-automator to activate Teams and navigate:
-- Navigate to a specific person or channel
tell application "Microsoft Teams" to activate
delay 0.5
tell application "System Events"
tell process "Microsoft Teams"
keystroke "g" using command down -- Open "Go to" search
delay 0.8
keystroke "TARGET_NAME" -- Type target name
delay 1.5
key code 36 -- Enter (select first result)
delay 1.5
end tell
end tell
MCP call:
{
"tool": "mcp__macos-automator__execute_script",
"arguments": {
"script_content": "tell application \"Microsoft Teams\" to activate\ndelay 0.5\ntell application \"System Events\"\n tell process \"Microsoft Teams\"\n keystroke \"g\" using command down\n delay 0.8\n keystroke \"Jay Pancholi\"\n delay 1.5\n key code 36\n delay 1.5\n end tell\nend tell",
"timeout_seconds": 10
}
}
Critical: Press Escape before Cmd+A to ensure the chat area has focus.
tell application "System Events"
tell process "Microsoft Teams"
key code 53 -- Escape (clear focus)
delay 0.3
keystroke "a" using command down -- Select all
delay 0.5
keystroke "c" using command down -- Copy
delay 0.8
end tell
end tell
do shell script "pbpaste" -- Return clipboard content
MCP call:
{
"tool": "mcp__macos-automator__execute_script",
"arguments": {
"script_content": "tell application \"System Events\"\n tell process \"Microsoft Teams\"\n key code 53\n delay 0.3\n keystroke \"a\" using command down\n delay 0.5\n keystroke \"c\" using command down\n delay 0.8\n end tell\nend tell\ndo shell script \"pbpaste\"",
"timeout_seconds": 5
}
}
Parse the raw text into structured JSON. See format documentation below.
Teams uses a specific text format when copying chat content. For detailed parsing patterns, see FORMAT.md.
Key patterns:
AuthorName\nDD/MM/YYYY H:MM am/pm\nContentBegin Reference...End Reference blocksemoji\nN emoji-name reactions.(GIF Image), (Image), has an attachment:, Url Preview for, Loop ComponentEveryoneEdited. suffixReturn parsed messages as JSON:
interface TeamsMessage {
author: string;
timestamp: string; // ISO 8601 format
content: string;
replyTo?: {
author: string;
preview: string;
};
reactions?: {
emoji: string;
count: number;
name: string;
}[];
attachments?: {
type: "gif" | "image" | "file" | "url" | "loop";
name?: string;
url?: string;
}[];
mentions?: string[];
edited?: boolean;
}
interface TeamsChat {
target: string; // Channel or person name
capturedAt: string; // ISO 8601 timestamp
messageCount: number;
messages: TeamsMessage[];
}
Example output:
{
"target": "Jay Pancholi",
"capturedAt": "2025-01-20T14:30:00.000Z",
"messageCount": 2,
"messages": [
{
"author": "Jay Pancholi",
"timestamp": "2025-01-15T14:34:00.000Z",
"content": "Hey Nathan, just checking in on the project status.\nLet me know when you have a moment.",
"reactions": [
{ "emoji": "👍", "count": 1, "name": "like" }
]
},
{
"author": "Nathan Vale",
"timestamp": "2025-01-15T14:45:00.000Z",
"content": "All good! I'll have the update ready by EOD.",
"replyTo": {
"author": "Jay Pancholi",
"preview": "Hey Nathan, just checking in on the project status."
}
}
]
}
If Teams is not running, the AppleScript will fail. Check first:
tell application "System Events"
set isRunning to (name of processes) contains "Microsoft Teams"
end tell
return isRunning
If Cmd+G search doesn't find the target:
If keystrokes don't work:
If clipboard is empty after capture:
User request: "Scrape my chat with Jay Pancholi from Teams"
Workflow:
Step 1: Navigate and capture (AppleScript)
-- Navigate and capture in one script
tell application "Microsoft Teams" to activate
delay 0.5
tell application "System Events"
tell process "Microsoft Teams"
-- Navigate
keystroke "g" using command down
delay 0.8
keystroke "Jay Pancholi"
delay 1.5
key code 36
delay 2.0
-- Capture
key code 53
delay 0.3
keystroke "a" using command down
delay 0.5
keystroke "c" using command down
delay 0.8
end tell
end tell
do shell script "pbpaste"
Step 2: Process with CLI
After capturing clipboard content, pipe it to the CLI:
pbpaste | bun run plugins/teams-scrape/src/cli.ts process "Jay Pancholi"
Expected output:
{
"target": "Jay Pancholi",
"capturedAt": "2026-01-20T14:30:00.000Z",
"isNewScrape": false,
"totalMessages": 45,
"newMessages": [
{
"id": "a1b2c3d4e5f6",
"author": "Jay Pancholi",
"timestamp": "2026-01-20T14:25:00.000Z",
"content": "The PR is ready for review"
}
],
"storagePath": "/Users/nathan/.config/teams-scrape/jay-pancholi.json"
}
~/.claude/logs/teams-scrape.jsonl for debugging~/.claude/logs/teams-scrape.jsonl