一键导入
polym-slack-digest
Generate Slack daily digests, highlight customer risks and action items, and publish summaries to Lark or local outputs.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generate Slack daily digests, highlight customer risks and action items, and publish summaries to Lark or local outputs.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | polym-slack-digest |
| description | Generate Slack daily digests, highlight customer risks and action items, and publish summaries to Lark or local outputs. |
| allowed-tools | Bash, Read, Write, Edit |
| version | 1.2.0 |
End-to-end Slack-to-Feishu daily intelligence pipeline. The skill handles the full lifecycle on Mira, Codex, and Claude Code:
11:00 and 16:00 in the configured timezone).| Platform | Lark docs | Scheduling | Durability | Retry/catch-up |
|---|---|---|---|---|
| Mira | Built-in Lark doc skills | Mira scheduled task | Durable | Mira watchdog + retry |
| Codex | Local Lark CLI | Durable Codex routines (MCP) | Durable UI | Durable retry routine (+15 min) |
| Claude Code | Local Lark CLI | Durable scheduled tasks (MCP) | Durable UI | Durable retry routine (+15 min) |
For Codex and Claude Code, all scheduled routines are durable host UI routines — they persist across session restarts and are visible/manageable in the host's routine/automation UI. Local OS schedulers (launchd, cron, systemd) are not used.
For one-off E2E runs, the current host agent handles synthesis via the HOST_AGENT_HANDOFF file. Do not look for a codex or claude executable for this path.
Working directory: All persistent files live under ~/.slack-daily-digest/. The skill creates this on first run.
Credential file: ~/.slack-daily-digest/credentials.json (mode 0600). Never echo credentials back to the user.
State files:
~/.slack-daily-digest/users_cache.json — Slack user ID → name/title/email~/.slack-daily-digest/channel_owner_cache.json — channel → BytePlus owner~/.slack-daily-digest/index.json — index doc URL + folder token + schedule~/.slack-daily-digest/routine.json — local Codex / Claude Code routine configuration~/.slack-daily-digest/routine_state.json — local routine slot attempts and catch-up state~/.slack-daily-digest/runs/<YYYY-MM-DD>/ — per-day pull/synth artifactsOutput language: All user-facing prompts, doc titles, and digest content default to English. The skill may switch to the user's working language only if the user explicitly asks.
Read ~/.slack-daily-digest/credentials.json:
index.json → run Phase B: Index Setup (§3)local_platform.json → run scripts/setup_lark_cli.py, then continue setup when readyPlatform detection is centralized in scripts/platform.py. Honor SLACK_DIGEST_PLATFORM=mira|local_agent when present; otherwise Mira-specific runtime hints select Mira, and all other environments use local-agent mode.
Show the user two parallel setup tracks in one message. Both must be completed before proceeding.
Display this verbatim (English):
Step 1 — Create your Slack App
- Go to https://api.slack.com/apps and click Create New App → From scratch.
- Name it
Mira Slack Digestand pick your workspace.- In OAuth & Permissions → User Token Scopes, add:
channels:history,channels:readgroups:history,groups:readim:history,im:readmpim:history,mpim:readusers:read,users:read.email- Click Install to Workspace and authorize.
- Copy the User OAuth Token (starts with
xoxp-...) and paste it back here.⚠️ Use the User Token, not the Bot Token — the digest reads channels you joined as a user.
After the user pastes the token, validate it:
curl -s -H "Authorization: Bearer $SLACK_USER_TOKEN" \
"https://slack.com/api/auth.test" | jq '.ok, .user, .team'
If ok=false, show the error and ask them to retry.
Step 2 — Create your Lark Custom Bot
- Go to https://open.feishu.cn/app and click Create Custom App.
- Name it
Mira Slack Digest Notifier.- In Permissions & Scopes, add:
im:message(send messages)im:message:send_as_botcontact:user.base:readonly(resolve email → open_id)docx:document(read/write docs)drive:drive(move docs to folder)- In Version Management & Release, publish v1.0 and wait for admin approval (or self-approve in trial workspaces).
- In Credentials & Basic Info, copy App ID and App Secret and paste both here.
Validate by acquiring a tenant access token:
curl -s -X POST "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" \
-H "Content-Type: application/json" \
-d "{\"app_id\":\"$LARK_APP_ID\",\"app_secret\":\"$LARK_APP_SECRET\"}" | jq '.code, .tenant_access_token'
code=0 means success.
Use scripts/save_credentials.py:
python3 ~/.claude/skills/slack-daily-digest/scripts/save_credentials.py \
--slack-token "xoxp-..." \
--lark-app-id "cli_..." \
--lark-app-secret "..." \
--lark-user-email "<the user's lark email>"
This writes ~/.slack-daily-digest/credentials.json with mode 0600.
Then proceed to Phase B.
Use scripts/platform.py to select the setup path.
When the platform is Codex / Claude Code local mode, tell the user:
I will create durable host UI routines — one per digest slot plus one retry routine (+15 min) per slot. Routines persist across session restarts and are visible in the Codex / Claude Code routine UI. An optional end-of-day catch-up is also available. All routines run
local_routine.py run-due, which deduplicates viaroutine_state.jsonso the same slot is never published twice.No local OS schedulers (launchd, cron, systemd) will be used.
Ask in one prompt — do not ask for times/timezone without also collecting working hours, retry, and catch-up:
Schedule configuration Please provide:
- Run times — e.g.
10:00, 14:00(comma-separated HH:MM)- Timezone — e.g.
Europe/London,Asia/Shanghai,Etc/GMT-1(default:Asia/Shanghai)- Working hours — e.g.
08:00-18:00(default:08:00-19:00)- Retry routines? — fires 15 min after each slot if main run missed/failed (default: yes)
- End-of-day catch-up? — fires ~30 min after last slot to retry missed runs (default: yes)
Ask the user (English): "Should I create a new Feishu folder named Slack Daily Digest to store all daily docs, or do you have an existing folder? If existing, paste the folder URL."
If Mira and new → use lark-drive-wiki-skill create-folder --name "Slack Daily Digest" --folder-token <root> and capture the token.
If Codex / Claude Code and new → use Lark CLI through scripts/lark_doc_client.py / lark-cli drive +create-folder; do not require a custom Lark app for document writing when Lark CLI user auth is available.
Use scripts/lark_doc_client.py so Mira keeps lark-docs-skill behavior and local mode uses lark-cli.
Title: Slack Daily Digest · Index
Body:
## This document is auto-maintained by Mira. Daily digests are listed below in reverse chronological order (newest on top). Monitoring scope: all Slack channels the configured user has joined; the channel list refreshes on every run.
## Daily Digest List (newest on top)
Move it into the folder through the same document client.
Use the run times, timezone, working hours, retry, and catch-up preferences collected in §3.0.
Persist to ~/.slack-daily-digest/index.json:
{
"index_doc_url": "https://www.feishu.cn/docx/...",
"index_doc_id": "...",
"folder_token": "...",
"timezone": "Etc/UTC",
"schedule": [{"hour": 11, "minute": 0, "timezone": "Etc/UTC", "tz": "+00:00", "window_hours": 19},
{"hour": 16, "minute": 0, "timezone": "Etc/UTC", "tz": "+00:00", "window_hours": 5}]
}
For local mode, also persist ~/.slack-daily-digest/routine.json:
{
"timezone": "Etc/UTC",
"slots": ["11:00", "16:00"],
"working_hours": {"start": "09:00", "end": "18:00"},
"catchup_hours": 6,
"check_interval_minutes": 10,
"agent": {"type": "auto", "command_template": null}
}
For Mira, use the loop skill (or platform's scheduled-task mechanism) to register:
slack-daily-digest run at each scheduled time.slack-daily-digest watchdog 20 minutes after each scheduled time.For Codex / Claude Code — durable host UI routines only. No local OS scheduler. No session-only routines.
setup-scheduler| Exit code | Meaning | Action |
|---|---|---|
host_unknown | Not in a Codex or Claude Code session | Stop. Do not create any scheduler. |
session_only_routine_not_allowed | Host detected but does not support durable routines | Stop. Report to user. |
host_durable_ui_routine_unavailable | mcp__scheduled-tasks__create_scheduled_task unavailable | Stop. Do not fall back to CronCreate or local OS scheduler. |
stable_path_required | SLACK_DIGEST_HOME is under /tmp or a test path | Ask user to confirm stable path (default ~/.slack-daily-digest) before continuing. |
scheduler_setup_blocked | Missing credentials, index, or runner config | Fix the listed items, then re-run. |
python3 scripts/local_routine.py setup-scheduler \
--slots "<times>" --timezone "<timezone>" --working-hours "<window>"
Prints a scheduler_setup_request JSON on success. Key fields:
host_scheduler — "codex" or "claude_code"scheduler_durability — always "durable_ui"host_instructions — host-specific creation notes (read host_instructions.create_method)host_routines — one descriptor per slot (main)retry_routines — one descriptor per slot at slot + 15 minoptional_catchup_routine — end-of-day catch-up descriptorlocal_scheduler_used — always falsesession_only_routine_used — always falseIf setup-scheduler exits non-zero, stop and show the error verbatim to the user.
Read host_instructions.host from the JSON.
host_instructions.host == "codex")Use mcp__scheduled-tasks__create_scheduled_task. All routines are durable and visible in the Codex automation UI.
Main routines — for each entry in host_routines[]:
mcp__scheduled-tasks__create_scheduled_task(
name = entry.name,
description = entry.description,
command = entry.command, # local_routine.py run-due --slot HH:MM --timezone TZ
cron = entry.cron, # e.g. "0 10 * * *"
env = entry.env, # SLACK_DIGEST_HOME + SLACK_DIGEST_PLATFORM
)
Retry routines — for each entry in retry_routines[]:
mcp__scheduled-tasks__create_scheduled_task(
name = entry.name, # e.g. "slack-digest-1000-retry"
description = entry.description,
command = entry.command,
cron = entry.cron, # e.g. "15 10 * * *"
env = entry.env,
)
Example for slots ["10:00", "16:00"] Europe/London:
slack-digest-1000 cron="0 10 * * *" → main 10:00
slack-digest-1000-retry cron="15 10 * * *" → retry 10:00
slack-digest-1600 cron="0 16 * * *" → main 16:00
slack-digest-1600-retry cron="15 16 * * *" → retry 16:00
Optional catch-up (ask user — see below). After creation record IDs (Step 3).
host_instructions.host == "claude_code")Use mcp__scheduled-tasks__create_scheduled_task. Scheduled tasks are durable, persist across session restarts, and are visible in the Claude Code Scheduled/Routines UI.
Do NOT use CronCreate. CronCreate jobs are session-only and auto-expire after 7 days — they are not allowed for production scheduled runs. If mcp__scheduled-tasks__create_scheduled_task is unavailable, stop with host_durable_ui_routine_unavailable — do not fall back to CronCreate, session-only jobs, launchd, or any local OS scheduler.
Stable path check: Before creating any scheduled task, confirm SLACK_DIGEST_HOME is not under /tmp or a test directory. If it is, stop and ask the user to confirm a stable path (default ~/.slack-daily-digest). Do not create durable tasks that point to /tmp.
Main routines — for each entry in host_routines[]:
mcp__scheduled-tasks__create_scheduled_task(
name = entry.name, # e.g. "slack-digest-1000"
description = entry.description,
command = entry.command, # local_routine.py run-due --slot HH:MM --timezone TZ
cron = entry.cron, # e.g. "0 10 * * *"
env = entry.env, # SLACK_DIGEST_HOME + SLACK_DIGEST_PLATFORM
)
Retry routines — for each entry in retry_routines[]:
mcp__scheduled-tasks__create_scheduled_task(
name = entry.name, # e.g. "slack-digest-1000-retry"
description = entry.description,
command = entry.command, # same run-due command; skips silently if slot succeeded
cron = entry.cron, # e.g. "15 10 * * *"
env = entry.env,
)
Confirm to the user that tasks are durable and visible in the Claude Code Scheduled/Routines UI.
⚠️ One-time permission approval required (Claude Code)
Claude Code scheduled tasks run with
permissionMode: default. The first time a task fires, it may pause and prompt for Bash/Write tool approval before executinglocal_routine.py run-due.Required action after creating routines:
- Open the Scheduled section in the Claude Code sidebar.
- Find the first digest task (e.g.
slack-digest-HHmm).- Click Run now.
- When prompted, approve Bash and Write tools.
- Tool approvals are stored on the task and auto-applied to all future scheduled runs.
Do not call scheduled setup fully verified until a task has actually executed
local_routine.py run-dueand a Lark digest doc URL appears inroutine_state.json.Alternative (pre-approve via settings): Add the following to the project
.claude/settings.local.jsoninside thepermissions.allowarray so that scheduled task sessions (which run from cwd~/.claude) auto-approve without prompting:"Bash(SLACK_DIGEST_HOME=<STATE_DIR> SLACK_DIGEST_PLATFORM=local_agent python3 <SKILL_DIR>/scripts/local_routine.py run-due *)", "Bash(<SKILL_DIR>/scripts/local_routine.py *)", "Write(<STATE_DIR>/**)"Replace
<STATE_DIR>withSLACK_DIGEST_HOMEvalue and<SKILL_DIR>with the skill path.
Jitter and timing: Scheduled tasks apply a small deterministic delay (up to ~10 min) at dispatch time. The retry routine (+15 min) and optional catch-up routine handle any slots that fire late or miss their window. Do not treat a delayed first fire as a failure.
Optional catch-up (ask user — see below). After creation record IDs (Step 3).
Create a durable routine from optional_catchup_routine using the same method as above (Step 2A or 2B). Do not ask the user again — catch-up preference was already collected in §3.0.
python3 scripts/local_routine.py set-backend --backend host_routine \
--durability durable_ui \
--routine-ids "<main-id-1>,<main-id-2>" \
--retry-ids "<retry-id-1>,<retry-id-2>" \
[--catchup-ids "<catchup-id>"]
Do not call set-backend unless durable routine creation succeeded and IDs were returned.
Report scheduler setup success only after all of the following are true:
mcp__scheduled-tasks__create_scheduled_task (or Codex equivalent) returned task IDsset-backend completed without errorroutine.json records scheduler_durability: "durable_ui" and the routine IDslocal_routine.py run-due and produced a Lark doc URL in routine_state.json (confirmed via "Run now" or a scheduled fire)If a routine was created but has never executed end-to-end, report setup as pending verification, not success. Instruct the user to find the task in the Scheduled/Routines UI, click "Run now", and approve Bash/Write tools if prompted.
host: codex | claude_code
scheduler backend: durable host UI routine
main routine IDs: [...]
retry routine IDs: [...]
catch-up routine ID: <id> | none
timezone: <TZ>
working hours: HH:MM-HH:MM
SLACK_DIGEST_HOME: <path>
local scheduler used: no
CronCreate used: no
session-only routine used: no
All routines invoke local_routine.py run-due --slot HH:MM --timezone TZ, which:
routine_state.json — skips immediately if this date+slot already succeededroutine.lock — prevents overlapping runsfinal_*.md → publish to Larksuccess only after a Lark doc URL is confirmed in result_*.jsontransient_error on network/API failures — retried by the +15 min retry routinecredential_error on auth failure — stops retrying, notifies userskipped_missed_window when the catch-up window expiresRun scripts/refresh_users_cache.py:
users_cache.json mtime > 7 days → call users.list paginated (Tier 2, sleep 1s between pages) and rewrite cache.This step is automatic at the start of every Daily Run.
Steps 1–8 below mirror the proven pipeline. All scripts live in scripts/.
| Step | Action | Script |
|---|---|---|
| 1 | List all conversations the user joined | scripts/list_channels.py |
| 2 | Compute time window from schedule | scripts/run_digest.py (inline) |
| 3 | Pull conversations.history per channel (sleep 0.3s, Tier 3) | scripts/pull_history.py |
| 4 | Resolve user IDs → names; replace <@Uxxx> mentions; identify BytePlus owner per channel | scripts/build_digest.py |
| 5 | AI synthesis — Executive Summary, Highlights, TODO, Pipeline, Risks, By-Channel (full message text, never truncate), Statistics | LLM call inside the conversation |
| 6 | Create per-day Feishu doc Slack Daily Digest · YYYY-MM-DD; move to folder; if doc already exists for today (16:00 run), edit-inplace replace the placeholder section | scripts/lark_doc_client.py |
| 7 | Pin the new entry to the top of the index list (insert_before the current first list item, NOT insert_after the title) | scripts/lark_doc_client.py |
| 8 | Lark IM notify the user | scripts/notify_lark.py |
import datetime, calendar
now_utc = datetime.datetime.utcnow()
window_hours = config["window_hours"] # 19 for 11:00, 5 for 16:00
oldest = calendar.timegm((now_utc - datetime.timedelta(hours=window_hours)).timetuple())
latest = calendar.timegm(now_utc.timetuple())
The digest MUST contain, in order:
Priority | Owner | Item | Deadline | Source channelPartner | Stage/Amount | Status | Owner | Health/ (slash-space). No truncation.## 🕙 16:00 Update (placeholder, to be filled) — only on the morning runThe natural insert_after against the index title appends to the bottom of the list (because all list items are siblings of the title). The correct anchor is the current first list item:
# Read index doc, find first "- **YYYY-MM-DD**" line
# Use insert_before with that line as the anchor
edits = [{
"mode": "insert_before",
"selection_with_ellipsis": "- **<previous-newest-date>** · [Slack Daily Digest · ...]",
"markdown": "- **<today>** · [Slack Daily Digest · <today>](<new-url>)"
}]
If the list is empty, fall back to insert_after against the title (only happens for the first-ever entry).
Triggered 20 minutes after each scheduled run.
python3 ~/.claude/skills/slack-daily-digest/scripts/watchdog.py --date <today> --slot <11:00|16:00>
Watchdog checks:
<today>'s entry. Missing → fail.On failure:
~/.slack-daily-digest/runs/<today>/error.log if it exists; otherwise inspect the latest pull/build artifacts.[60s, 300s, 900s].On success:
~/.slack-daily-digest/runs/<today>/watchdog.log.For Codex / Claude Code local mode, scripts/local_routine.py run-due owns schedule catch-up:
catchup_hours after the scheduled slot.skipped_missed_window and do not create a stale digest next day.~/.slack-daily-digest/routine.lock to prevent overlapping runs.The local routine invokes scripts/run_digest.py. One-off local mode pulls Slack history, builds digest_*.json, writes a host-agent handoff for synthesizing final_*.md from references/digest-template.md, publishes via LarkDocClient after final_*.md exists, updates the index newest-on-top, and writes ~/.slack-daily-digest/runs/<date>/result_<slot>.json.
After successful daily run, push to the configured user's Lark IM:
python3 ~/.claude/skills/slack-daily-digest/scripts/notify_lark.py \
--user-email "<from credentials>" \
--doc-url "<today's doc URL>" \
--highlights "@~/.slack-daily-digest/runs/<today>/highlights.md"
The notify script:
🔔 Slack Daily Digest · <today HH:MM configured timezone>| Error | Action |
|---|---|
Slack ratelimited | sleep 60s, retry up to 3× |
Slack invalid_auth / token_revoked | Append to error.log, IM the user "Slack token expired, please reconfigure", stop. |
users.info fails for one user | Fallback to User(<id>), do not block the run. |
Lark 99991663/4/5/8 or "token expire" | The agent harness's token-refresh hook handles this — do NOT re-implement here. |
Lark edit-inplace validation 1101 (locator not found) | Re-read doc with --output-format markdown, recompute the exact anchor (watch for full-width vs half-width punctuation), retry. |
| Lark doc create timeout | Save raw markdown to ~/.slack-daily-digest/runs/<today>/backup.md and IM the user. |
python3 ~/.claude/skills/slack-daily-digest/scripts/save_credentials.py --reschedule "11:00,16:00"
python3 ~/.claude/skills/slack-daily-digest/scripts/run_digest.py --oldest <epoch> --latest <epoch>
python3 ~/.claude/skills/slack-daily-digest/scripts/run_digest.py --date 2026-05-04 --slot 11:00
slack-daily-digest/
├── SKILL.md # this file
├── scripts/
│ ├── save_credentials.py # encrypt + persist creds
│ ├── refresh_users_cache.py # weekly users.list refresh
│ ├── list_channels.py # users.conversations
│ ├── pull_history.py # conversations.history per channel
│ ├── build_digest.py # ID resolution + owner detection
│ ├── run_digest.py # orchestrator (Steps 2-7)
│ ├── platform.py # Mira vs local-agent detection
│ ├── lark_doc_client.py # Mira docs / local Lark CLI abstraction
│ ├── setup_lark_cli.py # local Lark CLI readiness helper
│ ├── local_routine.py # local schedule + missed-run catch-up runner
│ ├── watchdog.py # T+20min verification + self-retry
│ ├── notify_lark.py # Lark IM notification
│ └── lark_helpers.py # shared: token, email→open_id, doc edit
└── references/
├── slack-app-setup.md # extended Slack onboarding docs
├── lark-bot-setup.md # extended Lark onboarding docs
└── digest-template.md # full v4 synthesis template
Research BytePlus products, APIs, SDKs, setup, quotas, regions, pricing, and troubleshooting using the bundled official documentation index, then verify claims on live docs. Use for requests for BytePlus documentation or official links.
Batch-generate Seedance 2.0 Mini videos via the BytePlus console BFF (Mini has no public API). Covers T2V, image/video reference (I2V/R2V), first/last frame, and human portraits. Triggers: "seedance mini", "dreamina mini", "用 mini 出视频".
Generate textured 3D models (GLB + PBR) from an image and/or text prompt via Volcengine Ark Seed3D (doubao-seed3d). Submits the async task, polls to completion, and downloads the .glb. Domestic Volcengine Ark only, not BytePlus overseas.
Generate videos with BytePlus Seedance. Use for Seedance text-to-video, image-to-video, camera control, and draft video workflows.
Generate images with BytePlus Seedream 4.x/4.5. Use for Seedream text-to-image, AI art, product images, and batch image generation.
Creates polished explainer videos from docs, URLs, PDFs, screenshots, or text. Use for product demos, announcement reels, repo videos, and requests like "make an explainer video" or "turn this repo into a video".