[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F6CW7RPB8N4WFZ6ZSBYZP2":3,"skill-same:01M6F6CW7RPB8N4WFZ6ZSBYZP2":83},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":60,"commitSha":61,"skillDirectory":62,"forksCount":63,"repositoryHtmlUrl":64,"repositoryHomepageUrl":65,"ownerHtmlUrl":66,"ownerBlogUrl":6,"ownerDescription":67,"updatedAt":68,"install":69,"resources":82},"01M6F6CW7RPB8N4WFZ6ZSBYZP2","action-items-todoist",null,"Extract action items from today's Granola\u002FGrain meetings, create Todoist tasks, complete fulfilled tasks, and draft meeting-triggered follow-up emails. Use when running the daily action items cron, post-meeting cron, or when user asks to process meeting action items. NOT for general email drafting without meeting context.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Faction-items-todoist\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000005","邮件与沟通","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"todoist","action items todoist","write","action-items","extract action items","granola","meeting action items","email drafting","up","context","gog","read","python3","Bash","cron","email","code","create","tasks","cli","2026-11-10 10:12:30",false,"---\nname: action-items-todoist\ndescription: \"Extract action items from today's Granola\u002FGrain meetings, create Todoist tasks, complete fulfilled tasks, and draft meeting-triggered follow-up emails. Use when running the daily action items cron, post-meeting cron, or when user asks to process meeting action items. NOT for general email drafting without meeting context.\"\nversion: 1.0.0\nlicense: MIT\nauthor: \"Martin Gontovnikas \u003Cmartin@hypergrowthpartners.com>\"\ncompatible-with: claude-code\ntags: [business, action-items]\nallowed-tools: Read, Bash(gog:*), Bash(mcporter:*), Bash(todoist-cli:*), Bash(python3:*), Bash(source:*), Bash(date:*), Glob, Grep, Write\n\n---\n# Action Items → Todoist + Email Drafts\n\n## Config — read before starting\nRead `..\u002Fconfig\u002Fuser.json` (resolves to `~\u002Fexecutive-assistant-skills\u002Fconfig\u002Fuser.json`).\nExtract and use throughout:\n- `name`, `full_name` — to identify your action items in meeting notes (e.g. \"Gonto (Martin)\")\n- `whatsapp` — for result delivery\n- `workspace` — absolute path to OpenClaw workspace\n\nDo not proceed until you have these values.\n\n## Debug Logging (MANDATORY)\nRead `..\u002Fconfig\u002FDEBUG_LOGGING.md` for the full convention. Use `python3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items \u003Clevel> \"\u003Cmessage>\" ['\u003Cdetails>']` at every key step. Log BEFORE and AFTER every external call (gog, mcporter, todoist-cli). On any error, log the full command and stderr before continuing.\n\n## Steps\n\n### 0. Check today's calendar for meetings (BOTH accounts)\nBefore querying Granola, get today's actual meetings from BOTH calendars to know what to expect:\n```bash\npython3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items INFO \"Starting action-items run\"\n\n# Get today's date in YYYY-MM-DD and tomorrow's\nTODAY=$(date -u -d \"$(TZ=America\u002FArgentina\u002FBuenos_Aires date +%Y-%m-%d)\" +%Y-%m-%d)\nTOMORROW=$(date -u -d \"$(TZ=America\u002FArgentina\u002FBuenos_Aires date -d '+1 day' +%Y-%m-%d)\" +%Y-%m-%d)\n\n# Check BOTH calendars\ngog --account {user.primary_email} --no-input calendar list primary --from \"${TODAY}T00:00:00-03:00\" --to \"${TOMORROW}T00:00:00-03:00\" --json 2>&1\ngog --account {user.work_email} --no-input calendar list primary --from \"${TODAY}T00:00:00-03:00\" --to \"${TOMORROW}T00:00:00-03:00\" --json 2>&1\n```\nLog the results: `python3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items DEBUG \"Calendar events found\" '{\"primary\": N, \"work\": M, \"total\": N+M}'`\n\nMerge events from both calendars. Filter for meetings with attendees (skip solo\u002Fpersonal events). This gives you the ground truth of what meetings happened today — use it to cross-check Granola results and catch any meetings Granola missed.\n\n**CRITICAL date syntax:** Use explicit ISO8601 dates with `-03:00` offset. Do NOT use relative expressions like `+1 day`, `today`, or `tomorrow` in gog flags — they may not be supported. Always compute the actual date strings.\n\n### 1. Get today's meetings from Granola\n\n**Timezone note:** Granola stores meeting times in UTC. For ART (UTC-3), querying \"today\" means using today's date AND tomorrow's date in UTC. E.g., for March 3 ART, query `custom_start: \"2026-03-03\"` and `custom_end: \"2026-03-04\"` to capture all ART-day meetings.\n\n```bash\nmcporter call granola list_meetings --args '{\"time_range\": \"custom\", \"custom_start\": \"\u003Ctoday YYYY-MM-DD>\", \"custom_end\": \"\u003Ctomorrow YYYY-MM-DD>\"}'\n```\nCollect meeting IDs and titles. Log: `python3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items INFO \"Granola meetings found\" '{\"count\": N, \"titles\": [...]}'`\n\n**Cross-check with calendar:** Compare Granola meetings against the calendar events from Step 0. If a calendar meeting with attendees has no Granola match (by time overlap within 15 min), log a warning — it may not have been recorded. Proceed with what Granola has, but note unmatched meetings in the output.\n\nSkip if no meetings (from either Granola or calendar).\n\n### 2. Query Granola for MY action items + email triggers\n```bash\nmcporter call granola query_granola_meetings --args '{\"query\": \"What are all of {user.name} ({user.full_name}) personal action items, follow-ups, and commitments from these meetings? Only things HE needs to do, not what others committed to. For each item, include the specific person, company, project, or candidate name involved — never use generic references. Also identify: any promises made to do ANYTHING via email (intros, follow-ups, sending docs, sharing info, connecting people, etc.), and whether each meeting was a FIRST meeting with that person\u002Fcompany or a follow-up.\", \"document_ids\": [\"\u003Cid1>\", \"\u003Cid2>\", ...]}'\n```\nPass ALL meeting IDs. Preserve citation links.\n\n### 3. Create Todoist tasks\n\n> **Note:** Load env in the same command — each shell call is a fresh session.\n\nRead `{user.workspace}\u002Fskills\u002Ftodoist-api\u002FSKILL.md` for CLI usage. For each action item:\n```bash\nsource {user.workspace}\u002F.env && todoist-cli add \"\u003Cactionable title>\" --description \"\u003Ccontext: meeting name, meeting date\u002Ftime, who requested, Granola link>\" --priority \u003C1-4> --labels \"\u003Crelevant>\"\n```\n\n**Task description MUST include:**\n- Meeting name (e.g. \"Braintrust Weekly\")\n- Meeting date and time (e.g. \"Wed Mar 5, 15:00 ART\")\n- Who requested \u002F context\n- Granola citation link\n\n**Rules:**\n- **Only your actions**: Granola summaries often list \"next steps\" without clear ownership. Be skeptical — if the action could belong to the other person (e.g. \"digest their own content\", \"check availability\", \"get back to us\"), do NOT create a task. When in doubt, create a FOLLOW-UP task (\"Ping X about Y\") rather than an ownership task.\n- **Capture commitments from others**: If the other person said they'd do something (e.g. \"I'll get back in 2 days\"), create a follow-up\u002Fping task with the appropriate due date, not a task to do the thing yourself.\n- **Specificity**: Every task MUST include specific name of person\u002Fcompany\u002Fproject\n- **Due dates**: If implied deadline, use `--due` with natural language\n- **Labels**: Tag: intro, follow-up, email, urgent as appropriate\n- **Priority**: 4=urgent\u002Ftime-sensitive, 3=promised deliverables, 2=general follow-ups, 1=normal\n\n#### No split tasks for sequential steps (MANDATORY)\nNever create separate tasks for steps that are part of the same workflow. If the action is \"prepare X then send X\" — that's ONE task, not two. Examples:\n- ❌ \"Build proposal\" + \"Send proposal\" → ✅ \"Build and send proposal\"\n- ❌ \"Write draft\" + \"Send email\" → ✅ \"Draft and send email to X\"\n- ❌ \"Review deck\" + \"Share deck\" → ✅ \"Review and share deck with X\"\n\nOne task per intent. The user will naturally do the steps in order.\n\n#### Todo dedup (MANDATORY)\nBefore creating a task, run a duplicate check against open Todoist tasks:\n1. Normalize proposed title (lowercase, trim punctuation, collapse whitespace)\n2. Search open tasks (`source {user.workspace}\u002F.env && todoist-cli list --filter \"!completed\"`) and compare normalized content\n3. Treat near-identical intro tasks as duplicates (e.g., \"Intro David to Marcos\" vs \"Intro David (n8n) to Marcos Nils\")\n4. If duplicate exists: do NOT create a new task; append meeting context to the existing task description when useful\n5. In output, report dedup decisions under `Skipped as duplicates:`\n\n### 4. Draft follow-up emails\nFor ANY email that needs drafting (intros, follow-ups, VC replies, sending docs, etc.):\n- **Read and follow `~\u002Fexecutive-assistant-skills\u002Femail-drafting\u002FSKILL.md`** — it is the single source of truth for all drafting rules, style, templates, humanization, and delivery\n- Identify draft triggers from meeting notes:\n  - Promised intros or follow-ups\n  - Promised docs\u002FPDFs\u002Fdecks\n  - First call with a VC or new lead\n  - Any promise to email someone\n- When in doubt about whether to draft → DRAFT IT\n- **Exception**: The proposal-only commitment rule below overrides this. If the commitment is to build a proposal first, do NOT draft — create a Todoist task only.\n- **HGP Deck**: `{user.workspace}\u002Fassets\u002FHGP_Deck_2025.pdf` — attach via `--attach {user.workspace}\u002Fassets\u002FHGP_Deck_2025.pdf`. Say \"Hypergrowth Partners deck\" in the email body (not \"one-pager\" or \"our deck\")\n- **When drafting intros to known contacts**: search sent emails for previous intros to them, use the same format, tone, and description\n\n#### Intro-specific hard requirements (MANDATORY)\nIf action items include intros, follow this exactly:\n1. Create **one separate intro draft per intro pair** (never merge multiple intros into one generic follow-up).\n2. Subject must be explicit: `Intro: \u003CPerson A> \u003C> \u003CPerson B>`.\n3. Body must include:\n   - one-line who each person is,\n   - one-line context for why this intro is happening,\n   - close with `I'll let you two take it from here.` and `{user.signature}`.\n4. **Never replace intro drafts with a generic recap email** like \"Great meeting today\" when intros were promised.\n5. If recipient emails are known, create Gmail drafts immediately; if any email is missing, still generate the full draft text and report `MISSING_EMAIL: \u003Cname>`.\n6. In the WhatsApp result, include an `Intro drafts created:` section listing each intro pair and whether it was drafted in Gmail or blocked by missing email.\n\n#### First VC \u002F first dealflow call follow-up (MANDATORY)\nWhen the meeting is a FIRST call with a VC or dealflow company:\n1. Create a first-meeting follow-up draft (unless blocked by proposal-only rule below).\n2. Use meeting-specific context in the body (what was discussed, explicit next steps, concrete offers), not generic pleasantries.\n3. Include your positioning (how you work) and attach `{user.workspace}\u002Fassets\u002FHGP_Deck_2025.pdf` when relevant.\n4. Allowed to use \"Great meeting today\" only for this first-meeting follow-up class.\n\n#### Proposal-only commitment rule (MANDATORY)\nIf you committed to \"build a proposal\" (or equivalent: proposal\u002Fdeck\u002Fscope draft to prepare first):\n- **Do NOT draft an outbound email yet**.\n- Create a **single** Todoist task to build AND send the proposal — e.g. \"Build and send advisory proposal to Gabriel (BairesDev)\".\n- Do NOT create separate tasks for \"build\" and \"send\" — that's redundant. One task covers the full lifecycle.\n\n### 5. Check if existing Todoist tasks were fulfilled in today's meetings\nAfter processing action items, also check if any **existing open Todoist tasks** were addressed\u002Fcompleted during today's meetings:\n1. List open tasks: `source {user.workspace}\u002F.env && todoist-cli list`\n2. For each meeting, check if the discussion covered or fulfilled any open task (e.g., \"Share AI strategy with Colin\" → discussed AI strategy directly with Colin in the call)\n3. If a task was clearly fulfilled in the meeting, **complete it**: `source {user.workspace}\u002F.env && todoist-cli complete \u003Ctask_id>`\n4. Report completed tasks in the output: \"✅ Completed: [task] — fulfilled during [meeting name]\"\n\nThis ensures Todoist stays clean and reflects what actually happened.\n\n## Error handling\n- If `gog calendar` fails: log the error with full command + stderr (`python3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items ERROR \"gog calendar failed\" '{\"command\": \"...\", \"stderr\": \"...\"}'`), continue with the other account and\u002For Granola-only.\n- If `mcporter call granola` fails with auth errors: report \"⚠️ Granola auth expired, run `mcporter auth granola --reset`\" and stop.\n- If `todoist-cli` fails: verify `.env` is sourced and token is valid. Report error.\n- If Grain MCP is unavailable: proceed with Granola-only extraction, note in output \"⚠️ Grain unavailable, results are Granola-only.\"\n- If any step fails, continue with remaining steps when possible — don't abort the entire run for one failure.\n\n## Deduplication (CRITICAL — prevents duplicate tasks)\n\n### Meeting-level dedup\n- **FIRST STEP before any processing**: Read `{user.workspace}\u002Fstate\u002Fprocessed-meetings-YYYY-MM-DD.json` (if it exists — array of Granola meeting IDs)\n- Skip any meetings whose ID is already in that list — do NOT re-process them\n- **Immediately after processing each meeting** (before moving to the next), append its Granola meeting ID to the file. Do NOT wait until the end — write after each meeting to prevent races with other crons.\n- This file is the single source of truth for \"was this meeting already processed today?\"\n\n> **Note:** Titles are fragile for recurring meetings that share the same name. Always dedup by Granola meeting ID.\n\n### Task-level dedup\n- Before creating ANY task, check BOTH open AND recently completed tasks for near-duplicates:\n  1. Fetch open tasks: `source {user.workspace}\u002F.env && todoist-cli list`\n  2. Fetch today's completed tasks: use Todoist Sync API `completed\u002Fget_all` with `since=\u003Ctoday 00:00 UTC>` or `source {user.workspace}\u002F.env && todoist-cli list --filter \"completed today\"` if supported\n  - Same person + same action intent = duplicate (e.g. \"Text Morgane about Hank\" and \"Text Morgane (Braintrust) after Hank call\")\n  - Normalize: lowercase, strip parentheticals, collapse whitespace\n  - If duplicate exists in EITHER open or completed → SKIP, do not create\n- Report skipped duplicates in output: \"⏭️ Skipped (already exists): [task]\"\n- **Why check completed tasks**: The user may have already completed a task created by a post-meeting cron earlier today. Recreating it is wrong — the work is done.\n\n### Why both layers matter\nPost-meeting crons fire per-meeting. The daily end-of-day cron processes ALL meetings. Without meeting-level dedup, the same meeting gets processed twice. Without task-level dedup, even if the meeting is re-processed (e.g. file write failed), individual tasks won't be duplicated.\n\n### Dedup is NON-NEGOTIABLE\nIf a meeting appears in `processed-meetings-YYYY-MM-DD.json`, do NOT process it again under any circumstances — even if you think the post-meeting cron \"might have missed something.\" The post-meeting cron already handled it. If it had issues, the user will ask for a re-run manually.\n\n## Output\n- Tasks created or drafts composed → list tasks with short summary\n- Tasks completed (fulfilled in meetings) → list with ✅\n- **Always name the specific meetings** processed (e.g. \"from Fyxer call, Braintrust weekly, HGP Staff\") — never say \"from today's meetings\"\n- No meetings or no action items → NO_REPLY\n\n## Cross-check with Grain (ACTIVE — transcript-level verification)\nGrain MCP is available with full transcript access. After extracting action items from Granola:\n\n1. **Find the meeting in Grain**: `mcporter call grain.list_attended_meetings --args '{}'`\n   Note: Grain's schema does not support `start_date`\u002F`end_date` filters. Call with empty args and filter the results manually by `start_datetime` to match today's date range.\n2. **Fetch the transcript**: `mcporter call grain.fetch_meeting_transcript --args '{\"meeting_id\": \"\u003Cgrain_meeting_id>\"}'`\n3. **Cross-check each action item against the transcript**:\n   - Is the action item actually assigned to you, or to the other person?\n   - Are there commitments with timelines that Granola missed (e.g. \"I'll get back in 2 days\")?\n   - Are there action items Granola dropped entirely?\n4. **Prefer Grain's transcript** over Granola's summary when they conflict\n5. Also use `grain.fetch_meeting_notes` for Grain's own AI-generated notes as a second opinion\n\nIf a Grain meeting can't be found (e.g. recording wasn't on), fall back to Granola only + skepticism rules.\n\n#### Matching Grain to Granola meetings\nMatch by time overlap (within 15 min of start time), not by title. Grain and Granola may name the same meeting differently. If no Grain match found within the time window, proceed without cross-check.\n\n## Overview\n\nExtracts action items from daily meetings via Granola\u002FGrain, creates Todoist tasks with deduplication, completes fulfilled tasks, and drafts follow-up emails triggered by meeting commitments.\n\n## Prerequisites\n\n- `gog` CLI configured with both Gmail\u002FCalendar accounts\n- `mcporter` with Granola and Grain MCP connections authenticated\n- `todoist-cli` installed with valid API token in workspace `.env`\n- OpenClaw workspace with `skill_log.py` and `audit_log.py` scripts\n- WhatsApp delivery endpoint configured in `user.json`\n\n## Instructions\n\nSee the Steps section above (Steps 0 through 5) for the full execution workflow.\n\n## Examples\n\n```bash\n# Trigger via cron or manual invocation\n# The skill reads user.json, queries both calendars, pulls Granola meetings,\n# extracts action items, creates Todoist tasks, drafts emails, and notifies via WhatsApp.\n# Example output: \"From Fyxer call: 2 tasks created, 1 intro draft (Gmail), 1 task completed\"\n```\n\n## Resources\n\n- [Todoist REST API](https:\u002F\u002Fdeveloper.todoist.com\u002Frest\u002Fv2\u002F)\n- [Granola API](https:\u002F\u002Fgranola.ai\u002Fdocs)\n- [Grain API](https:\u002F\u002Fgrain.com\u002Fdocs)\n- [Gmail API](https:\u002F\u002Fdevelopers.google.com\u002Fgmail\u002Fapi)\n\n","# Action Items → Todoist + Email Drafts\n\n## Config — read before starting\nRead `..\u002Fconfig\u002Fuser.json` (resolves to `~\u002Fexecutive-assistant-skills\u002Fconfig\u002Fuser.json`).\nExtract and use throughout:\n- `name`, `full_name` — to identify your action items in meeting notes (e.g. \"Gonto (Martin)\")\n- `whatsapp` — for result delivery\n- `workspace` — absolute path to OpenClaw workspace\n\nDo not proceed until you have these values.\n\n## Debug Logging (MANDATORY)\nRead `..\u002Fconfig\u002FDEBUG_LOGGING.md` for the full convention. Use `python3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items \u003Clevel> \"\u003Cmessage>\" ['\u003Cdetails>']` at every key step. Log BEFORE and AFTER every external call (gog, mcporter, todoist-cli). On any error, log the full command and stderr before continuing.\n\n## Steps\n\n### 0. Check today's calendar for meetings (BOTH accounts)\nBefore querying Granola, get today's actual meetings from BOTH calendars to know what to expect:\n```bash\npython3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items INFO \"Starting action-items run\"\n\n# Get today's date in YYYY-MM-DD and tomorrow's\nTODAY=$(date -u -d \"$(TZ=America\u002FArgentina\u002FBuenos_Aires date +%Y-%m-%d)\" +%Y-%m-%d)\nTOMORROW=$(date -u -d \"$(TZ=America\u002FArgentina\u002FBuenos_Aires date -d '+1 day' +%Y-%m-%d)\" +%Y-%m-%d)\n\n# Check BOTH calendars\ngog --account {user.primary_email} --no-input calendar list primary --from \"${TODAY}T00:00:00-03:00\" --to \"${TOMORROW}T00:00:00-03:00\" --json 2>&1\ngog --account {user.work_email} --no-input calendar list primary --from \"${TODAY}T00:00:00-03:00\" --to \"${TOMORROW}T00:00:00-03:00\" --json 2>&1\n```\nLog the results: `python3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items DEBUG \"Calendar events found\" '{\"primary\": N, \"work\": M, \"total\": N+M}'`\n\nMerge events from both calendars. Filter for meetings with attendees (skip solo\u002Fpersonal events). This gives you the ground truth of what meetings happened today — use it to cross-check Granola results and catch any meetings Granola missed.\n\n**CRITICAL date syntax:** Use explicit ISO8601 dates with `-03:00` offset. Do NOT use relative expressions like `+1 day`, `today`, or `tomorrow` in gog flags — they may not be supported. Always compute the actual date strings.\n\n### 1. Get today's meetings from Granola\n\n**Timezone note:** Granola stores meeting times in UTC. For ART (UTC-3), querying \"today\" means using today's date AND tomorrow's date in UTC. E.g., for March 3 ART, query `custom_start: \"2026-03-03\"` and `custom_end: \"2026-03-04\"` to capture all ART-day meetings.\n\n```bash\nmcporter call granola list_meetings --args '{\"time_range\": \"custom\", \"custom_start\": \"\u003Ctoday YYYY-MM-DD>\", \"custom_end\": \"\u003Ctomorrow YYYY-MM-DD>\"}'\n```\nCollect meeting IDs and titles. Log: `python3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items INFO \"Granola meetings found\" '{\"count\": N, \"titles\": [...]}'`\n\n**Cross-check with calendar:** Compare Granola meetings against the calendar events from Step 0. If a calendar meeting with attendees has no Granola match (by time overlap within 15 min), log a warning — it may not have been recorded. Proceed with what Granola has, but note unmatched meetings in the output.\n\nSkip if no meetings (from either Granola or calendar).\n\n### 2. Query Granola for MY action items + email triggers\n```bash\nmcporter call granola query_granola_meetings --args '{\"query\": \"What are all of {user.name} ({user.full_name}) personal action items, follow-ups, and commitments from these meetings? Only things HE needs to do, not what others committed to. For each item, include the specific person, company, project, or candidate name involved — never use generic references. Also identify: any promises made to do ANYTHING via email (intros, follow-ups, sending docs, sharing info, connecting people, etc.), and whether each meeting was a FIRST meeting with that person\u002Fcompany or a follow-up.\", \"document_ids\": [\"\u003Cid1>\", \"\u003Cid2>\", ...]}'\n```\nPass ALL meeting IDs. Preserve citation links.\n\n### 3. Create Todoist tasks\n\n> **Note:** Load env in the same command — each shell call is a fresh session.\n\nRead `{user.workspace}\u002Fskills\u002Ftodoist-api\u002FSKILL.md` for CLI usage. For each action item:\n```bash\nsource {user.workspace}\u002F.env && todoist-cli add \"\u003Cactionable title>\" --description \"\u003Ccontext: meeting name, meeting date\u002Ftime, who requested, Granola link>\" --priority \u003C1-4> --labels \"\u003Crelevant>\"\n```\n\n**Task description MUST include:**\n- Meeting name (e.g. \"Braintrust Weekly\")\n- Meeting date and time (e.g. \"Wed Mar 5, 15:00 ART\")\n- Who requested \u002F context\n- Granola citation link\n\n**Rules:**\n- **Only your actions**: Granola summaries often list \"next steps\" without clear ownership. Be skeptical — if the action could belong to the other person (e.g. \"digest their own content\", \"check availability\", \"get back to us\"), do NOT create a task. When in doubt, create a FOLLOW-UP task (\"Ping X about Y\") rather than an ownership task.\n- **Capture commitments from others**: If the other person said they'd do something (e.g. \"I'll get back in 2 days\"), create a follow-up\u002Fping task with the appropriate due date, not a task to do the thing yourself.\n- **Specificity**: Every task MUST include specific name of person\u002Fcompany\u002Fproject\n- **Due dates**: If implied deadline, use `--due` with natural language\n- **Labels**: Tag: intro, follow-up, email, urgent as appropriate\n- **Priority**: 4=urgent\u002Ftime-sensitive, 3=promised deliverables, 2=general follow-ups, 1=normal\n\n#### No split tasks for sequential steps (MANDATORY)\nNever create separate tasks for steps that are part of the same workflow. If the action is \"prepare X then send X\" — that's ONE task, not two. Examples:\n- ❌ \"Build proposal\" + \"Send proposal\" → ✅ \"Build and send proposal\"\n- ❌ \"Write draft\" + \"Send email\" → ✅ \"Draft and send email to X\"\n- ❌ \"Review deck\" + \"Share deck\" → ✅ \"Review and share deck with X\"\n\nOne task per intent. The user will naturally do the steps in order.\n\n#### Todo dedup (MANDATORY)\nBefore creating a task, run a duplicate check against open Todoist tasks:\n1. Normalize proposed title (lowercase, trim punctuation, collapse whitespace)\n2. Search open tasks (`source {user.workspace}\u002F.env && todoist-cli list --filter \"!completed\"`) and compare normalized content\n3. Treat near-identical intro tasks as duplicates (e.g., \"Intro David to Marcos\" vs \"Intro David (n8n) to Marcos Nils\")\n4. If duplicate exists: do NOT create a new task; append meeting context to the existing task description when useful\n5. In output, report dedup decisions under `Skipped as duplicates:`\n\n### 4. Draft follow-up emails\nFor ANY email that needs drafting (intros, follow-ups, VC replies, sending docs, etc.):\n- **Read and follow `~\u002Fexecutive-assistant-skills\u002Femail-drafting\u002FSKILL.md`** — it is the single source of truth for all drafting rules, style, templates, humanization, and delivery\n- Identify draft triggers from meeting notes:\n  - Promised intros or follow-ups\n  - Promised docs\u002FPDFs\u002Fdecks\n  - First call with a VC or new lead\n  - Any promise to email someone\n- When in doubt about whether to draft → DRAFT IT\n- **Exception**: The proposal-only commitment rule below overrides this. If the commitment is to build a proposal first, do NOT draft — create a Todoist task only.\n- **HGP Deck**: `{user.workspace}\u002Fassets\u002FHGP_Deck_2025.pdf` — attach via `--attach {user.workspace}\u002Fassets\u002FHGP_Deck_2025.pdf`. Say \"Hypergrowth Partners deck\" in the email body (not \"one-pager\" or \"our deck\")\n- **When drafting intros to known contacts**: search sent emails for previous intros to them, use the same format, tone, and description\n\n#### Intro-specific hard requirements (MANDATORY)\nIf action items include intros, follow this exactly:\n1. Create **one separate intro draft per intro pair** (never merge multiple intros into one generic follow-up).\n2. Subject must be explicit: `Intro: \u003CPerson A> \u003C> \u003CPerson B>`.\n3. Body must include:\n   - one-line who each person is,\n   - one-line context for why this intro is happening,\n   - close with `I'll let you two take it from here.` and `{user.signature}`.\n4. **Never replace intro drafts with a generic recap email** like \"Great meeting today\" when intros were promised.\n5. If recipient emails are known, create Gmail drafts immediately; if any email is missing, still generate the full draft text and report `MISSING_EMAIL: \u003Cname>`.\n6. In the WhatsApp result, include an `Intro drafts created:` section listing each intro pair and whether it was drafted in Gmail or blocked by missing email.\n\n#### First VC \u002F first dealflow call follow-up (MANDATORY)\nWhen the meeting is a FIRST call with a VC or dealflow company:\n1. Create a first-meeting follow-up draft (unless blocked by proposal-only rule below).\n2. Use meeting-specific context in the body (what was discussed, explicit next steps, concrete offers), not generic pleasantries.\n3. Include your positioning (how you work) and attach `{user.workspace}\u002Fassets\u002FHGP_Deck_2025.pdf` when relevant.\n4. Allowed to use \"Great meeting today\" only for this first-meeting follow-up class.\n\n#### Proposal-only commitment rule (MANDATORY)\nIf you committed to \"build a proposal\" (or equivalent: proposal\u002Fdeck\u002Fscope draft to prepare first):\n- **Do NOT draft an outbound email yet**.\n- Create a **single** Todoist task to build AND send the proposal — e.g. \"Build and send advisory proposal to Gabriel (BairesDev)\".\n- Do NOT create separate tasks for \"build\" and \"send\" — that's redundant. One task covers the full lifecycle.\n\n### 5. Check if existing Todoist tasks were fulfilled in today's meetings\nAfter processing action items, also check if any **existing open Todoist tasks** were addressed\u002Fcompleted during today's meetings:\n1. List open tasks: `source {user.workspace}\u002F.env && todoist-cli list`\n2. For each meeting, check if the discussion covered or fulfilled any open task (e.g., \"Share AI strategy with Colin\" → discussed AI strategy directly with Colin in the call)\n3. If a task was clearly fulfilled in the meeting, **complete it**: `source {user.workspace}\u002F.env && todoist-cli complete \u003Ctask_id>`\n4. Report completed tasks in the output: \"✅ Completed: [task] — fulfilled during [meeting name]\"\n\nThis ensures Todoist stays clean and reflects what actually happened.\n\n## Error handling\n- If `gog calendar` fails: log the error with full command + stderr (`python3 {user.workspace}\u002Fscripts\u002Fskill_log.py action-items ERROR \"gog calendar failed\" '{\"command\": \"...\", \"stderr\": \"...\"}'`), continue with the other account and\u002For Granola-only.\n- If `mcporter call granola` fails with auth errors: report \"⚠️ Granola auth expired, run `mcporter auth granola --reset`\" and stop.\n- If `todoist-cli` fails: verify `.env` is sourced and token is valid. Report error.\n- If Grain MCP is unavailable: proceed with Granola-only extraction, note in output \"⚠️ Grain unavailable, results are Granola-only.\"\n- If any step fails, continue with remaining steps when possible — don't abort the entire run for one failure.\n\n## Deduplication (CRITICAL — prevents duplicate tasks)\n\n### Meeting-level dedup\n- **FIRST STEP before any processing**: Read `{user.workspace}\u002Fstate\u002Fprocessed-meetings-YYYY-MM-DD.json` (if it exists — array of Granola meeting IDs)\n- Skip any meetings whose ID is already in that list — do NOT re-process them\n- **Immediately after processing each meeting** (before moving to the next), append its Granola meeting ID to the file. Do NOT wait until the end — write after each meeting to prevent races with other crons.\n- This file is the single source of truth for \"was this meeting already processed today?\"\n\n> **Note:** Titles are fragile for recurring meetings that share the same name. Always dedup by Granola meeting ID.\n\n### Task-level dedup\n- Before creating ANY task, check BOTH open AND recently completed tasks for near-duplicates:\n  1. Fetch open tasks: `source {user.workspace}\u002F.env && todoist-cli list`\n  2. Fetch today's completed tasks: use Todoist Sync API `completed\u002Fget_all` with `since=\u003Ctoday 00:00 UTC>` or `source {user.workspace}\u002F.env && todoist-cli list --filter \"completed today\"` if supported\n  - Same person + same action intent = duplicate (e.g. \"Text Morgane about Hank\" and \"Text Morgane (Braintrust) after Hank call\")\n  - Normalize: lowercase, strip parentheticals, collapse whitespace\n  - If duplicate exists in EITHER open or completed → SKIP, do not create\n- Report skipped duplicates in output: \"⏭️ Skipped (already exists): [task]\"\n- **Why check completed tasks**: The user may have already completed a task created by a post-meeting cron earlier today. Recreating it is wrong — the work is done.\n\n### Why both layers matter\nPost-meeting crons fire per-meeting. The daily end-of-day cron processes ALL meetings. Without meeting-level dedup, the same meeting gets processed twice. Without task-level dedup, even if the meeting is re-processed (e.g. file write failed), individual tasks won't be duplicated.\n\n### Dedup is NON-NEGOTIABLE\nIf a meeting appears in `processed-meetings-YYYY-MM-DD.json`, do NOT process it again under any circumstances — even if you think the post-meeting cron \"might have missed something.\" The post-meeting cron already handled it. If it had issues, the user will ask for a re-run manually.\n\n## Output\n- Tasks created or drafts composed → list tasks with short summary\n- Tasks completed (fulfilled in meetings) → list with ✅\n- **Always name the specific meetings** processed (e.g. \"from Fyxer call, Braintrust weekly, HGP Staff\") — never say \"from today's meetings\"\n- No meetings or no action items → NO_REPLY\n\n## Cross-check with Grain (ACTIVE — transcript-level verification)\nGrain MCP is available with full transcript access. After extracting action items from Granola:\n\n1. **Find the meeting in Grain**: `mcporter call grain.list_attended_meetings --args '{}'`\n   Note: Grain's schema does not support `start_date`\u002F`end_date` filters. Call with empty args and filter the results manually by `start_datetime` to match today's date range.\n2. **Fetch the transcript**: `mcporter call grain.fetch_meeting_transcript --args '{\"meeting_id\": \"\u003Cgrain_meeting_id>\"}'`\n3. **Cross-check each action item against the transcript**:\n   - Is the action item actually assigned to you, or to the other person?\n   - Are there commitments with timelines that Granola missed (e.g. \"I'll get back in 2 days\")?\n   - Are there action items Granola dropped entirely?\n4. **Prefer Grain's transcript** over Granola's summary when they conflict\n5. Also use `grain.fetch_meeting_notes` for Grain's own AI-generated notes as a second opinion\n\nIf a Grain meeting can't be found (e.g. recording wasn't on), fall back to Granola only + skepticism rules.\n\n#### Matching Grain to Granola meetings\nMatch by time overlap (within 15 min of start time), not by title. Grain and Granola may name the same meeting differently. If no Grain match found within the time window, proceed without cross-check.\n\n## Overview\n\nExtracts action items from daily meetings via Granola\u002FGrain, creates Todoist tasks with deduplication, completes fulfilled tasks, and drafts follow-up emails triggered by meeting commitments.\n\n## Prerequisites\n\n- `gog` CLI configured with both Gmail\u002FCalendar accounts\n- `mcporter` with Granola and Grain MCP connections authenticated\n- `todoist-cli` installed with valid API token in workspace `.env`\n- OpenClaw workspace with `skill_log.py` and `audit_log.py` scripts\n- WhatsApp delivery endpoint configured in `user.json`\n\n## Instructions\n\nSee the Steps section above (Steps 0 through 5) for the full execution workflow.\n\n## Examples\n\n```bash\n# Trigger via cron or manual invocation\n# The skill reads user.json, queries both calendars, pulls Granola meetings,\n# extracts action items, creates Todoist tasks, drafts emails, and notifies via WhatsApp.\n# Example output: \"From Fyxer call: 2 tasks created, 1 intro draft (Gmail), 1 task completed\"\n```\n\n## Resources\n\n- [Todoist REST API](https:\u002F\u002Fdeveloper.todoist.com\u002Frest\u002Fv2\u002F)\n- [Granola API](https:\u002F\u002Fgranola.ai\u002Fdocs)\n- [Grain API](https:\u002F\u002Fgrain.com\u002Fdocs)\n- [Gmail API](https:\u002F\u002Fdevelopers.google.com\u002Fgmail\u002Fapi)\n\n",{"name":5,"tags":54,"author":56,"license":15,"version":57,"description":7,"allowed-tools":58,"compatible-with":59},[55,32],"business","Martin Gontovnikas \u003Cmartin@hypergrowthpartners.com>","1.0.0","Read, Bash(gog:*), Bash(mcporter:*), Bash(todoist-cli:*), Bash(python3:*), Bash(source:*), Bash(date:*), Glob, Grep, Write","claude-code","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Faction-items-todoist",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":70,"prompt":80,"zipAvailable":81},[71,74,77],{"tool":72,"command":73},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill action-items-todoist",{"tool":75,"command":76},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill action-items-todoist",{"tool":78,"command":79},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill action-items-todoist","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Faction-items-todoist\u002FSKILL.md",true,[],[84,96,106,115,121,128],{"skillNo":85,"name":86,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":92,"publishedAt":49},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[89],{"categoryNo":90,"categoryName":91,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[93,94,95],"model","model hierarchy","model hierarchy skill",{"skillNo":97,"name":98,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":102,"publishedAt":49},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[101],{"categoryNo":90,"categoryName":91,"taxonomyType":21,"taxonomyTypeDesc":22},[103,104,105],"MCP","zoom","zoom mcp",{"skillNo":107,"name":108,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":109,"categories":110,"keywords":111,"publishedAt":49},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[112,113,114,104],"sdk","zoom apps sdk","apps",{"skillNo":116,"name":117,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":118,"categories":119,"keywords":120,"publishedAt":49},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[117],{"skillNo":122,"name":123,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":127,"publishedAt":49},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[126],{"categoryNo":90,"categoryName":91,"taxonomyType":21,"taxonomyTypeDesc":22},[123],{"skillNo":129,"name":130,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":133,"publishedAt":49},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[130]]