| name | slack-capture |
| description | Capture raw Slack channel or DM digests into Inbox for later core-agent curation. Use for on-demand Slack capture or ad-hoc Slack reports. |
| version | 2.0.0 |
| author | Hermes Agent |
| license | MIT |
| platforms | ["windows","linux","macos"] |
| metadata | {"hermes":{"tags":["vault","slack","capture","vault-capture"],"related_skills":["obsidian"]}} |
slack-capture
Use this when the user asks to retrieve Slack messages, capture daily Slack logs, or answer queries like "今日の私の発言を取得して".
This is a capture-only skill: it writes RAW Slack material into Inbox/{YYYY-MM-DD}/slack/{channel}.md or returns an ad-hoc report. There is no routing or DM-based sorting. Distilling into Wiki is the core agent's later curate step.
Token modes
Multi-workspace user-token-only capture
When the user wants to treat extra Slack workspaces as sub-workspace information sources, keep the primary Hermes Slack gateway on the main workspace and add one workspace-specific User OAuth token per source workspace (for example SLACK_USER_TOKEN_WORKSPACE_B). Do not require a bot for read-only capture if the user explicitly says bot is not needed.
For “all messages today” capture, use search.messages with on:MM/DD/YYYY as the baseline for all messages visible to that user token, then supplement with conversations.list + conversations.history for richer channel/file metadata. Group by channel and write one raw digest per channel under Inbox/{YYYY-MM-DD}/slack/{channel}.md (multi-workspace captures may prefix the workspace into the channel slug, e.g. {workspace}-{channel}.md, to keep them distinct within the day's slack/ folder). See references/multi-workspace-user-token-capture.md for the manifest, config pattern, CLI shape, verification commands, and pitfalls.
Bot token mode: event/channel/DM history
Use SLACK_BOT_TOKEN (xoxb-...) for Hermes gateway and for channels/DMs where the bot is allowed to read history.
Typical scopes:
chat:write
app_mentions:read
channels:history
groups:history
im:history
im:read
im:write
users:read
files:read / files:write if attachments are captured
Pitfall: conversations.list may show public channels where the bot is not a member. conversations.history can still fail with not_in_channel. That is not a capture failure; it means the bot cannot see that channel until invited or granted access.
User token mode: cross-channel search
Use SLACK_USER_TOKEN (xoxp-... or xoxe.xoxp-...) when the user wants a cross-channel search over messages visible to their own Slack account, especially "my messages today".
Required user scopes:
search:read
users:read for resolving SLACK_ALLOWED_USERS to a Slack username
Pitfall: Slack search.messages rejects bot tokens with not_allowed_token_type. Do not keep retrying with SLACK_BOT_TOKEN; ask the user to add a User OAuth Token to .hermes/.env as SLACK_USER_TOKEN.
Setup checklist for user-token search
- Open Slack App management:
https://api.slack.com/apps.
- Select the existing Hermes app (the one you created for this vault).
- Go to OAuth & Permissions.
- Add User Token Scopes:
search:read, users:read.
- Reinstall to Workspace.
- Copy the User OAuth Token and add it to the active Hermes profile env file:
SLACK_USER_TOKEN=xoxp-...
- Restart/reload the Hermes process that needs the new env value.
Ad-hoc "today's user messages" workflow
- Load
.hermes/.env and read:
SLACK_USER_TOKEN
SLACK_ALLOWED_USERS (target user id)
- Call
users.info with the user token to resolve the username.
- Build local-date query variants:
from:{username} after:{YYYY-MM-DD} before:{YYYY-MM-DD+1}
from:<@{user_id}> after:{YYYY-MM-DD} before:{YYYY-MM-DD+1}
- Call
search.messages with sort=timestamp, sort_dir=asc, and dedupe by channel.id + ts.
- Report channel, local time, and text. Do not print token values.
Reusable script: scripts/slack_today_my_messages.py.
Daily / "run for yesterday" workflow (on-demand or cron)
When this skill is invoked with wording like "run it for yesterday" (either by an on-demand user instruction or by cron), treat it as a capture job for the previous local date (Asia/Tokyo unless the job says otherwise), not as an ad-hoc text-only report.
- Resolve the target date explicitly from the live clock and record it in output/logs.
- Use user-token search (
SLACK_USER_TOKEN) for cross-channel authored messages and @mentions visible to the user.
- Use bot-token channel/DM history (
SLACK_BOT_TOKEN) only as a supplement for channels/DMs the bot can read. not_in_channel is expected; rate limits are partial-coverage warnings, not a reason to rewrite good captures with poorer data.
- Dedupe by
channel.id + ts, then group by channel and resolve each target Inbox/{YYYY-MM-DD}/slack/{channel}.md.
- Before any write or rerun mutation, search
Daily/{date}.md for the target's exact wikilink. A linked digest has handed off to the core and MUST be skipped without repair, append, overwrite, or recreation.
- Write a new raw digest when the target is absent. For an existing unlinked pre-aggregation digest, verify counts/content and preserve richer fields rather than replacing it with a simplified reconstruction.
- If an unlinked digest body is good but frontmatter is stale, perform a frontmatter-only repair to the current Output spec and preserve the body.
- Before finalizing, check that no temporary scripts/files remain. If there are zero qualifying messages and no changes, report silence only when explicitly requested.
- When the reusable script is insufficient and a one-off helper is needed, write it under a temporary path such as
.tmp/, run it, then remove it and verify removal before finalizing. Never print Slack token values in script output or logs.
See references/yesterday-cron-capture.md for the preservation safeguards, references/frontmatter-repair.md for the frontmatter-only repair pattern, and references/same-day-cron-capture.md for same-day digest-writing capture details, bot-supplement rate-limit handling, and cleanup/reporting checks.
起動方法(on-demand)
既定 = on-demand:ユーザーが Daily のジョブリストからコアエージェントに指示し、コアが Hermes に委譲する。
手動 invoke コマンド
hermes chat -q のスキル指定は -s <skill>(--skill / --workdir というフラグは無い)。vault ルートに cd してから呼ぶ。日本語 Windows では呼び出し前に PYTHONUTF8=1 を設定する(cp932 デコード起因の出力欠落防止 → [[.codex/skills/hermes-query/SKILL.md]])。
cd "<vault root>"
hermes chat -q "Load the slack-capture skill and run it for today: capture Slack messages visible to the user (authored / mentioned / DM / private), grouped by channel, into Inbox/<today>/slack/{channel}.md. Capture only — no routing, no curated edits." -s slack-capture -Q --source core-agent
hermes chat -q "Load the slack-capture skill and run it for yesterday: capture Slack messages visible to the user, grouped by channel, into Inbox/<yesterday>/slack/{channel}.md. Before changing an existing digest, check Daily/<yesterday>.md for its exact source wikilink; skip linked files. Preserve/repair only unlinked pre-aggregation digests." -s slack-capture -Q --source core-agent
既存環境の same-day / next-morning cron は過渡期ジョブとして現状維持する。新規登録・変更はせず、Daily ジョブリストから on-demand で実行する。
Self-edit boundary
このスキルは 自分の SKILL.md / references / config を autonomous に編集しない。drift を検知した場合は Inbox/{YYYY-MM-DD}/clippings/hermes-obs-slack-capture.md に observation note を新規作成する。frontmatter は共通 6 fields(title, type: capture, status: inbox, tags, created, updated)に加え、affected_path / observed_at / evidence / proposed_change / source: "hermes:observation:slack-capture:<ISO8601>" を必須とする。
Vault boundaries
- Raw capture target:
Inbox/{YYYY-MM-DD}/slack/ (one dated parent folder per day; create it only on a day Slack actually produces messages).
- One digest file per channel:
Inbox/{YYYY-MM-DD}/slack/{channel}.md. No routing, no slack-channel-map.yaml, no _unsorted/ — capture only.
- Do not write curated Wiki/Daily notes from this skill. Distilling/distributing is the core agent's later step.
- Before touching an existing digest, search
Daily/{date}.md for its exact source wikilink. If linked, ownership has handed off to the core: skip it unchanged. Only an unlinked pre-aggregation digest may be repaired or appended.
Output spec (REQUIRED — single source of truth for digest files)
This section is the authoritative format every digest file written by this skill MUST conform to.
Mirrors .codex/rules/inbox-routing.md §5.3.
Digest path & filename
Parent folder: Inbox/{YYYY-MM-DD}/slack/ (the dated parent folder owns the date; filenames carry NO date prefix).
- Regular / private channel:
Inbox/{YYYY-MM-DD}/slack/{channel}.md (drop #, keep slug as-is)
- DM:
Inbox/{YYYY-MM-DD}/slack/dm-{counterpart}.md (counterpart = display name, slugified)
Required frontmatter fields
---
title: "Slack digest - #{channel} - {YYYY-MM-DD}"
type: "capture"
status: "inbox"
tags: ["slack", "capture"]
source: "slack:digest:{channel}:{YYYY-MM-DD}"
channel: "#{channel}"
channel_id: "{Cxxxx}"
is_dm: true | false
is_private: true | false
participants: ["display_name_1", "display_name_2"]
user_authored: true | false
user_mentioned: true | false
message_count: <int>
fetched_at: <ISO8601
{}
{}
source: is a frontmatter signal string (identifier), NOT a filesystem path — keep it as slack:digest:{channel-slug}:{date} regardless of where the file lives.
Forbidden / deprecated values
| Field | Forbidden | Use instead |
|---|
source | "slack:daily:..." | "slack:digest:{channel-slug}:{YYYY-MM-DD}" |
source | "slack:digest:{channel_id}:..." | "slack:digest:{channel-slug}:..." (slug, not id) |
created | full ISO with time | YYYY-MM-DD only (timestamp belongs in fetched_at) |
Thread-parent context (REQUIRED)
When a qualifying the user message is a thread reply (i.e. its Slack envelope has thread_ts != ts):
- ALSO capture the thread parent message (the original post at
thread_ts).
- Capture any siblings (other replies in the same thread) that are immediately adjacent in time to the user's message, so the context is readable.
- Mark each captured non-the user message with
reason: thread_context (vs authored / mentioned for primary qualifications).
- Order by
ts ascending so the thread reads naturally.
Without thread parent context, a reply like "賛成です" is meaningless to the reader.
Attachment download (REQUIRED — auth-aware)
Slack file URLs (https://files.slack.com/... and per-team file links) require Slack token authentication to download the real bytes. A naked GET without Authorization: Bearer <token> returns the Slack login HTML page (<!DOCTYPE html>...), which has been observed saved with a .png extension and is useless.
Required behavior:
- Use
SLACK_BOT_TOKEN (or SLACK_USER_TOKEN if files were shared in a context only the user can see) in the Authorization: Bearer ... header for ALL attachment downloads.
- After download, validate magic bytes match the declared content-type:
- PNG:
89 50 4E 47 0D 0A 1A 0A
- JPEG:
FF D8 FF
- GIF:
47 49 46 38
- PDF:
25 50 44 46
- If validation fails (e.g. the bytes start with
<!DOCTYPE html or <html), DO NOT keep the file — log a one-line warning and write a placeholder pointer in the digest instead:
- 📎 {filename} → ⚠️ download failed (auth or permission); permalink: {url}
- Save valid attachments to
Inbox/{YYYY-MM-DD}/attachments/slack/{channel}/{ts}-{original-filename} and link relatively from the digest.
References
references/user-token-search.md — SLACK_USER_TOKEN workaround for cross-channel search.messages.
references/multi-workspace-capture-cli.md — pattern for keeping one Slack workspace as the Hermes gateway while using a Slack Web API based capture CLI for additional read-only workspaces.
references/yesterday-cron-capture.md — preservation rules for re-runs (don't overwrite richer existing digests).