Install
openclaw skills install @cargo-ai/meeting-prepThe moment an external meeting is booked on anyone's Google Calendar, one card lands in Slack: who is joining, the open deal, the line that mattered last time, one recent public event, and an opener grounded in your positioning, with reschedules and cancellations threaded under it. Triggers: "tell me in slack whenever someone books", "a card for every external meeting", "briefer on our google calendar", "watch our calendars for new meetings with prospects", "post who is joining before each booked meeting". Cargo CDK, defineAgent, Google Calendar agent trigger, domain-wide delegation, getEvent, Slack postMessage, gtm_ native models, workspace context, webSearch. Skip when: you want one company looked into now, in this chat, which is research-account; or you want the call scribed after it happened, which is call-capture.
openclaw skills install @cargo-ai/meeting-prepState: to-be-approved. Deploy-verified against a live workspace: yes, on 2026-10-09, with the
trigger on one Google Workspace calendar: booking an external meeting posted one card, a move and a
move of the whole series each threaded one reply, a series-wide RSVP posted nothing, and the
cancellation threaded :x: Cancelled. Treat Done when below as the acceptance test and review
cargo-ai cdk plan before deploying. Make no outcome claim for this skill until it is approved.
Nobody walks into a call cold, and nobody has to ask for the brief. The moment someone on the team books a meeting with an outside attendee, Google Calendar wakes one agent, and one card lands in a locked Slack channel, mentioning the organizer:
When the meeting moves, gains an outside attendee or changes title, a short reply lands in the card's thread with what changed. When it is cancelled, the thread says so. A description edit or an RSVP posts nothing.
Each meeting gets its own conversation with the agent, so the conversation is the record: there is no ledger to keep and no schedule to miss a late booking. With domain-wide delegation, the trigger watches every calendar in the Google Workspace, not only the calendar of whoever installed it. Internal meetings never wake the agent.
The agent reads the shared gtm_accounts, gtm_contacts, gtm_opportunities and gtm_activities
native models and the event itself, and never writes to any of them. Its only output is the Slack
post, in a channel locked on the action.
Brief the team in #sales-prep whenever anyone books a meeting with someone outside the company.
Illustrative output, fictional records:
:calendar: *Tue Oct 6, 10:00 PT · Discovery* with *Fabrikam* — @dana
*Company* — Freight-audit software for mid-market shippers; 240 employees; tier A
*Deal* — Discovery, $38,000, closes Dec 12, next step: confirm data-team owner
*Who's joining* — Priya Shah, VP Revenue Operations, linkedin.com/in/priya-shah-fab
Sam Okafor, Data Engineer, no profile on file (not answered)
*Last time* — Sep 18: "Our routing breaks every time Salesforce changes a field."
*Recent* — Hired a Head of Data on Sep 30 (fabrikam.example/news/head-of-data)
*Call tip* — Their pain is schema drift, which is where we win against hand-built
routing. Lead with the field-change story. Ask: "Who gets paged when a routing
rule silently stops matching?"
_Sources: calendar event · fabrikam.example/news/head-of-data_
↳ :arrows_counterclockwise: Moved: Tue Oct 6 10:00 → Wed Oct 7 14:00 PT
The card landed within a minute of the booking; the next day's reschedule threaded under it, and an agenda edit in between posted nothing.
This folder is a worked example: real CDK resources written for some other company. The job is
to end up with the code your company would have written, in your project, and an agent does the
adapting. If the cargo-cdk skill is in your session it carries the long form of this; if not, this
is enough.
cargo-ai cdk add cookbook/meeting-prep writes this example to infra/meeting-prep/ and this
procedure to .claude/skills/meeting-prep/. No project yet?
cargo-ai cdk init <dir> --cookbook meeting-prep && cd <dir> && npm install does both. If you
are reading this from the project's .claude/skills/, the install already happened — start at
step 2.gtm_ models from another pipeline, rewire the imports
to the existing ones and drop the copies; two resources with one slug is a collision at deploy.https://www.googleapis.com/auth/calendar.readonly and
https://www.googleapis.com/auth/admin.directory.user.readonly, then the connector is created in
Cargo with the Workspace domain and an admin email. For one person: connect it with OAuth
instead. Either way default: true in infra/connectors/google-calendar.ts binds it.cargo-ai cdk add connector/slack). Set channelId in infra/agents/briefer.ts to a channel id
(C…) from that connector's channel autocomplete, and invite the bot. references/card.md is
the card shape.## Decisions section in
your copy of this file.node --import tsx evals/contract.mjs from this skill's folder, then
npm run check && cargo-ai cdk plan from the project root. Show the diff, and deploy only on an
explicit yes: cargo-ai cdk deploy. Never cdk init --force into a non-empty directory.cargo-ai storage record create on each gtm_ model; Cargo generates the ids, so create the
account first and use its id), book a test meeting with an outside address on a watched
calendar, and walk Done when line by line with evidence. Watched calendars are enrolled within
the hour after deploy.Derive before you ask. An input with a lookup is looked up, not asked.
| Input | Kind | How it is answered | Why it matters |
|---|---|---|---|
| how Google Calendar connects | asked | whole team (domain-wide delegation: the Workspace domain and an admin email, after the admin authorizes Cargo's client id) or one person (OAuth). cargo-ai connection connector list shows one already there. | One person's OAuth only ever sees that person's calendar. A team that expects everyone briefed and connects with OAuth gets one rep's meetings. |
which users (infra/agents/briefer.ts) | asked | userScope: "all", or "selected" with the emails of the team that sells in users. | "all" briefs recruiters' and investors' external meetings too. A list keeps the channel to sales. |
channelId (infra/agents/briefer.ts) | asked | the Slack channel id (C…) the cards may land in, read from the connector's channel autocomplete. Invite the bot. | Cards quote deal amounts and call lines. Locked so they never land in a customer shared channel. |
| where accounts, contacts, deals and activity come from | derived | cargo-ai storage model list: the shared gtm_ models another pipeline already fills, a CRM swap (crm-backed), or nothing yet, in which case the card says what it could not find. | Empty models make every card a web-only brief. It still works, but the deal and last-time lines never appear. |
LLM connector and model (infra/connectors/anthropic.ts) | value | derived: cargo-ai connection connector list. languageModel is a placeholder to set. | It is what every briefing is billed against. |
Checked before moving on, not after the deploy:
channelId is a C… id the Slack connector can see, and the bot is in that channelnode --import tsx evals/contract.mjs passes| Variation | When it is right | How | What it costs |
|---|---|---|---|
one-calendar | A founder or a single rep, no Workspace admin involved | Connect Google Calendar with OAuth instead of domain-wide delegation; nothing else changes | Only that person's meetings are briefed. |
sales-team-only | The Workspace has people whose external meetings are not sales | Set userScope: "selected" and users to the list of emails on the trigger | A new rep is not briefed until someone adds them. |
internal-too | Internal prep meetings for a deal should be briefed as well | Set externalOnly: false and teach the prompt which internal meetings count (a deal name in the title, say) | Every 1:1 and standup wakes the agent. Without a rule in the prompt it posts noise. |
per-rep-dm | Reps want their own cards, not a shared channel | Drop the channel lock and post to the organizer's Slack id from listUsers; keep a rule in the prompt that refuses any other target | The agent picks the destination. Keep the rule, or a card can land somewhere it should not. |
crm-backed | Accounts, contacts, deals and activity live in HubSpot, Salesforce or Attio | Swap each gtm_ model for a connector-backed one extracted with every column (HubSpot companies, contacts, deals, plus meetings, calls and notes for activity) and map their columns onto the shared names in the prompt | One more OAuth connector and a sync interval: activity logged after the last sync misses the card. |
morning-digest | The team prefers one list each morning to a card per booking | Replace the calendar trigger with a weekday cron and give the agent listEvents for the day; keep a ledger model so a re-run posts nothing | A meeting booked after the morning run is never briefed, and a ledger the agent writes is state you now have to keep. |
infra/agents/briefer.prompt.ts)
Anyone outside the company can write an invite. An agent that obeys the agenda can be told to
post a deal to the wrong place or skip a brief. The contract fails if that line leaves the prompt.channelId is locked on postMessage. (infra/agents/briefer.ts) Cards carry deal amounts
and quoted call lines. A channel the agent picks is how one lands in a customer shared channel.
The agent fills only the thread, to reply under its own card.infra/agents/briefer.prompt.ts) The card is
posted once, on booking. An update speaks only when the time, an outside attendee or the title
changed, and then as a reply in the thread. Re-posting the brief on every RSVP is how the channel
gets muted in a week.infra/agents/briefer.prompt.ts) The
agent reads the clock first and posts nothing for a meeting that has ended. On a recurring
series it only threads a move for the same occurrence, never for another one, and it posts at
most once per turn. Google reports changes to old events too: a series edited or an RSVP on the
whole series re-reports its past occurrences. Without these lines, one RSVP change posted cards
for 2024 meetings and 27 "moved" replies into one thread during the live test.infra/agents/briefer.ts) gtm_accounts,
gtm_contacts, gtm_opportunities and gtm_activities are read-only on uses, and no action
writes to the calendar. A briefer that can update a deal changes a stage from a guess.infra/agents/briefer.prompt.ts, Rules) A LinkedIn URL that
does not belong to the attendee gets clicked in front of them. "No profile on file" is the
correct output when nothing was returned.infra/agents/briefer.ts) A cron misses the
meeting booked between runs and needs a ledger to dedupe. The contract fails on a cron trigger;
the morning-digest variation is the deliberate exception.node --import tsx evals/contract.mjs passescargo-ai cdk plan reports the agent, the four gtm_ models, the three connectors and the two
foldersreferences/card.md shape, mentioning the organizer, with sources on
the last line:x: Cancelled under the cardgtm_activities row it came fromThere is no per-record fan-out and no enrichment provider. Each booking, material change or
cancellation is one agent run: a few queries on the workspace's own native models, one calendar
read, up to two web searches, a Slack user lookup and one post, billed as LLM tokens through the
Anthropic connector plus the searches; maxSteps caps it per event. Before the plan, read the live
price of the Slack and Google Calendar actions on uses with
cargo-ai orchestration action list <action> --kind connector --integration-slug <slug>, and say
each number out loud. Non-material updates end in one short reply with nothing posted.
call-capture (its promoted claims are what the call tip leans on, and its entries can fill
gtm_activities), account-scoring (the tier on the card), and research-account (a deeper brief
on one account when the card is not enough).