Read ../config/user.json (resolves to ~/executive-assistant-skills/config/user.json).
Extract and use throughout:
name, full_name — to identify your action items in meeting notes (e.g. "Gonto (Martin)")whatsapp — for result deliveryworkspace — absolute path to OpenClaw workspaceDo not proceed until you have these values.
Read ../config/DEBUG_LOGGING.md for the full convention. Use python3 {user.workspace}/scripts/skill_log.py action-items <level> "<message>" ['<details>'] 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.
Before querying Granola, get today's actual meetings from BOTH calendars to know what to expect:
python3 {user.workspace}/scripts/skill_log.py action-items INFO "Starting action-items run"
# Get today's date in YYYY-MM-DD and tomorrow's
TODAY=$(date -u -d "$(TZ=America/Argentina/Buenos_Aires date +%Y-%m-%d)" +%Y-%m-%d)
TOMORROW=$(date -u -d "$(TZ=America/Argentina/Buenos_Aires date -d '+1 day' +%Y-%m-%d)" +%Y-%m-%d)
# Check BOTH calendars
gog --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
gog --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
Log the results: python3 {user.workspace}/scripts/skill_log.py action-items DEBUG "Calendar events found" '{"primary": N, "work": M, "total": N+M}'
Merge events from both calendars. Filter for meetings with attendees (skip solo/personal 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.
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.
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.
mcporter call granola list_meetings --args '{"time_range": "custom", "custom_start": "<today YYYY-MM-DD>", "custom_end": "<tomorrow YYYY-MM-DD>"}'
Collect meeting IDs and titles. Log: python3 {user.workspace}/scripts/skill_log.py action-items INFO "Granola meetings found" '{"count": N, "titles": [...]}'
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.
Skip if no meetings (from either Granola or calendar).
mcporter 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/company or a follow-up.", "document_ids": ["<id1>", "<id2>", ...]}'
Pass ALL meeting IDs. Preserve citation links.
Note: Load env in the same command — each shell call is a fresh session.
Read {user.workspace}/skills/todoist-api/SKILL.md for CLI usage. For each action item:
source {user.workspace}/.env && todoist-cli add "<actionable title>" --description "<context: meeting name, meeting date/time, who requested, Granola link>" --priority <1-4> --labels "<relevant>"
Task description MUST include:
Rules:
--due with natural languageNever 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:
One task per intent. The user will naturally do the steps in order.
Before creating a task, run a duplicate check against open Todoist tasks:
source {user.workspace}/.env && todoist-cli list --filter "!completed") and compare normalized contentSkipped as duplicates:
For ANY email that needs drafting (intros, follow-ups, VC replies, sending docs, etc.):
~/executive-assistant-skills/email-drafting/SKILL.md — it is the single source of truth for all drafting rules, style, templates, humanization, and delivery{user.workspace}/assets/HGP_Deck_2025.pdf — attach via --attach {user.workspace}/assets/HGP_Deck_2025.pdf. Say "Hypergrowth Partners deck" in the email body (not "one-pager" or "our deck")If action items include intros, follow this exactly:
Intro: <Person A> <> <Person B>.I'll let you two take it from here. and {user.signature}.MISSING_EMAIL: <name>.Intro drafts created: section listing each intro pair and whether it was drafted in Gmail or blocked by missing email.When the meeting is a FIRST call with a VC or dealflow company:
{user.workspace}/assets/HGP_Deck_2025.pdf when relevant.If you committed to "build a proposal" (or equivalent: proposal/deck/scope draft to prepare first):
After processing action items, also check if any existing open Todoist tasks were addressed/completed during today's meetings:
source {user.workspace}/.env && todoist-cli list
source {user.workspace}/.env && todoist-cli complete <task_id>
This ensures Todoist stays clean and reflects what actually happened.
gog calendar fails: log the error with full command + stderr (python3 {user.workspace}/scripts/skill_log.py action-items ERROR "gog calendar failed" '{"command": "...", "stderr": "..."}'), continue with the other account and/or Granola-only.mcporter call granola fails with auth errors: report "⚠️ Granola auth expired, run mcporter auth granola --reset" and stop.todoist-cli fails: verify .env is sourced and token is valid. Report error.{user.workspace}/state/processed-meetings-YYYY-MM-DD.json (if it exists — array of Granola meeting IDs)Note: Titles are fragile for recurring meetings that share the same name. Always dedup by Granola meeting ID.
source {user.workspace}/.env && todoist-cli list
completed/get_all with since=<today 00:00 UTC> or source {user.workspace}/.env && todoist-cli list --filter "completed today" if supportedPost-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.
If 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.
Grain MCP is available with full transcript access. After extracting action items from Granola:
mcporter call grain.list_attended_meetings --args '{}'
Note: Grain's schema does not support start_date/end_date filters. Call with empty args and filter the results manually by start_datetime to match today's date range.mcporter call grain.fetch_meeting_transcript --args '{"meeting_id": "<grain_meeting_id>"}'
grain.fetch_meeting_notes for Grain's own AI-generated notes as a second opinionIf a Grain meeting can't be found (e.g. recording wasn't on), fall back to Granola only + skepticism rules.
Match 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.