Send notifications and briefings to Slack/Teams channels. Monitor channels for urgent messages.
SLACK_BOT_TOKEN — Bot OAuth token (xoxb-...)SLACK_SIGNING_SECRET — for webhook verification (if receiving events)TEAMS_WEBHOOK_URL — Incoming webhook connector URLconversations.list#cfo-briefing — daily briefing delivery#deal-alerts — Salesforce deal notifications#competitive-intel — competitor news alertspip install slack_sdkchat:write, channels:read, and files:write scopes.SLACK_BOT_TOKEN=$(op read "op://Jarvis/Slack-Bot/token")
TEAMS_WEBHOOK_URL=$(op read "op://Jarvis/Teams-Webhook/url")
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
def get_slack_client():
return WebClient(token=os.environ["SLACK_BOT_TOKEN"])
def post_message(channel, text, blocks=None):
"""Post a message to a Slack channel with optional Block Kit formatting."""
client = get_slack_client()
try:
response = client.chat_postMessage(
channel=channel,
text=text, # fallback for notifications
blocks=blocks, # rich Block Kit layout
unfurl_links=False,
)
return response["ts"] # message timestamp (ID)
except SlackApiError as e:
if e.response["error"] == "channel_not_found":
raise ValueError(f"Channel {channel} not found — check bot is invited")
elif e.response["error"] == "not_in_channel":
# Auto-join public channels
client.conversations_join(channel=channel)
return post_message(channel, text, blocks)
raise
def post_daily_briefing(channel, briefing_data):
"""Post formatted daily CFO briefing."""
blocks = [
{"type": "header", "text": {"type": "plain_text", "text": f"CFO Daily Briefing — {briefing_data['date']}"}},
{"type": "divider"},
{"type": "section", "text": {"type": "mrkdwn", "text": f"*Key Metrics*\n• ARR: {briefing_data.get('arr', 'N/A')}\n• NRR: {briefing_data.get('nrr', 'N/A')}\n• Pipeline: {briefing_data.get('pipeline', 'N/A')}"}},
{"type": "divider"},
{"type": "section", "text": {"type": "mrkdwn", "text": f"*Calendar Highlights*\n{briefing_data.get('calendar', 'No meetings today')}"}},
{"type": "divider"},
{"type": "section", "text": {"type": "mrkdwn", "text": f"*Action Items*\n{briefing_data.get('actions', 'None pending')}"}},
{"type": "context", "elements": [{"type": "mrkdwn", "text": "Generated by Jarvis | OpenClaw Workspace"}]},
]
return post_message(channel, f"Daily Briefing — {briefing_data['date']}", blocks)
def post_deal_alert(channel, deal):
"""Post a Salesforce deal alert with color-coded attachment."""
color = "#2eb886" if deal["stage"] == "Closed Won" else "#dc3545" if "churn" in deal.get("risk", "").lower() else "#daa520"
attachments = [{
"color": color,
"blocks": [
{"type": "section", "text": {"type": "mrkdwn", "text": f"*{deal['name']}*\nAccount: {deal['account']} | Amount: ${deal['amount']:,.0f}\nStage: {deal['stage']} | Close Date: {deal['close_date']}"}},
{"type": "context", "elements": [{"type": "mrkdwn", "text": f"Owner: {deal.get('owner', 'Unknown')} | Source: Salesforce"}]},
]
}]
client = get_slack_client()
return client.chat_postMessage(channel=channel, text=f"Deal Alert: {deal['name']}", attachments=attachments)
def post_competitive_alert(channel, headline, source, summary):
"""Post a competitive intelligence notification."""
blocks = [
{"type": "section", "text": {"type": "mrkdwn", "text": f":rotating_light: *Competitive Alert*\n*{headline}*\nSource: {source}\n\n{summary}"}},
]
return post_message(channel, f"Competitive Alert: {headline}", blocks)
import requests, os, json
def post_teams_message(title, body, color="0076D7"):
"""Post an Adaptive Card to Teams via incoming webhook."""
webhook_url = os.environ["TEAMS_WEBHOOK_URL"]
card = {
"type": "message",
"attachments": [{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{"type": "TextBlock", "size": "Medium", "weight": "Bolder", "text": title},
{"type": "TextBlock", "text": body, "wrap": True},
],
}
}]
}
resp = requests.post(webhook_url, json=card, timeout=10)
resp.raise_for_status()
return resp.status_code
memory/slack-posts.jsonl for audit.not_in_channel: Bot auto-joins public channels; for private channels, an admin must invite the bot.invalid_auth / token_revoked: Token has expired — re-issue via Slack app admin panel and update 1Password.retry_after header.#cfo-briefing.post_message() for visibility.salesforce-soql skill can trigger post_deal_alert() when deals change stage.monitor agent can call post_competitive_alert() when competitor news is detected.memory/incidents.jsonl per workspace rules.