Skip to main content

slack

Operate Justin's Slack on his behalf via user-token (xoxp-) API — read channels/DMs/threads, search messages, post as Justin, react. Workspace: SignLab. Single-workspace today; designed to multi-tenant later.

설치로 이동

소스 정보

저장소
justinsensei/apollo-backup
최근 소스 활동
2026년 8월 12일 03:04
감지된 SKILL.md 언어
영어
스타
1
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
3 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
slack
description
Operate Justin's Slack on his behalf via user-token (xoxp-) API — read channels/DMs/threads, search messages, post as Justin, react. Workspace: SignLab. Single-workspace today; designed to multi-tenant later.
version
1.0.0
author
Apollo
license
MIT
metadata
{"hermes":{"tags":["slack","signlab","messaging","second-brain"],"related_skills":["obsidian","google-workspace"]}}
# Slack (SignLab workspace, user-token mode) ## ⚠️ READ FIRST — operating posture This Slack access is **user-token** (`xoxp-`), not a bot. That means: - **Anything you post appears as Justin himself**, not as "Apollo". There is no visible distinction in Slack between "Justin typed this on his phone" and "Apollo posted this via the API on Justin's behalf." Co-workers will react to your posts as if Justin wrote them. - You can read every channel, DM, and group DM Justin can read. That includes potentially sensitive conversations. Treat what you see as you would treat reading Justin's email: extract what's useful, don't quote verbatim to third parties. - **Default to read-only operations.** Posting and reacting are allowed but require either (a) Justin explicitly asking ("post X in #foo") or (b) a cron/skill flow that Justin has previously approved. - **Never post in a public channel without confirming with Justin first** unless the post is in a channel he's explicitly designated for automation (none today). ## Workspace - **SignLab** (only workspace today) - Auth: env var `SLACK_USER_TOKEN` (starts with `xoxp-`) - App name in Slack: "Apollo" (or similar) — Justin owns it - Scopes installed: channels/groups/im/mpim history+read, users:read, search:read, files:read, reactions:read, chat:write, reactions:write If a write op 403s with `missing_scope`, the response includes the needed scope. Tell Justin which scope is missing rather than guessing. ## The wrapper: `slack` CLI There is a thin Python wrapper at `scripts/slack.py` (installed onto PATH as `slack`). Call it via the standard pattern: ```bash ~/.hermes/hermes-agent/venv/bin/python \ ~/.hermes/skills/social-media/slack/scripts/slack.py <subcommand> [args] ``` All subcommands print JSON to stdout by default (parseable). Use `--text` for human-readable rendering of message lists. ### Subcommands #### `whoami` Returns the authed user's identity. Sanity-check the token works: ```bash slack whoami # {"user_id": "U01ABC...", "team_id": "T01...", "team": "SignLab", "user": "justin"} ``` #### `channels [--type public|private|im|mpim|all] [--member-only] [--limit N]` List channels. Default: `--type all --member-only` (only conversations Justin is in — usually what you want). ```bash slack channels --type public --member-only # [{"id": "C01...", "name": "general", "is_member": true, "is_private": false, "num_members": 42}, ...] ``` Use `--type im` to list DMs, `--type mpim` for group DMs. IM entries include `user` (the other person's user ID). #### `read <channel_id> [--since 24h|2d|2025-05-15] [--limit N] [--text]` Fetch recent messages from a channel/DM/thread. Resolves user IDs to display names automatically. ```bash slack read C0123456789 --since 24h --text # 2026-05-20 09:14 alice: heads up, the deploy is rolling # 2026-05-20 09:16 bob: ack ``` `--since` accepts: `Nh` / `Nd` / `Nw` (relative) or `YYYY-MM-DD` (absolute, midnight UTC). Default: `24h`. JSON mode (no `--text`) returns each message with `ts`, `user`, `user_name`, `text`, `thread_ts`, `reply_count`, `reactions`. #### `thread <channel_id> <thread_ts> [--text]` Fetch a full thread (parent + all replies). ```bash slack thread C0123 1716200000.123456 --text ``` #### `search <query> [--limit N] [--text]` Use Slack's `search.messages` endpoint. Supports Slack search modifiers: `from:@alice`, `in:#general`, `before:2025-05-01`, `after:`, `has::eyes:`, `-from:@bot`, quoted phrases, etc. ```bash slack search 'in:#engineering deploy after:2025-05-15' --limit 20 ``` JSON mode returns hits with `permalink` (so you can cite back to Justin in Telegram). #### `post <channel_id> <text> [--thread <ts>] [--broadcast]` Post a message. **Posts as Justin.** Confirm with Justin first unless the ask is explicit. ```bash slack post C0123 "Will follow up after the 2pm." slack post C0123 "ack" --thread 1716200000.123456 ``` `--broadcast` (only with `--thread`): also surface the thread reply to the main channel ("Also send to channel" in the Slack UI). Returns `{"ok": true, "ts": "1716200099.123", "channel": "C0123", "permalink": "https://signlab.slack.com/archives/C0123/p17162..."}`. #### `react <channel_id> <message_ts> <emoji>` Add a reaction. `emoji` without colons (`thumbsup`, not `:thumbsup:`). ```bash slack react C0123 1716200000.123456 eyes ``` #### `permalink <channel_id> <message_ts>` Get a permalink for citation. Often the most useful op when answering Justin: cite the source. #### `user <user_id>` Resolve a user ID to profile info (name, real_name, email if available, title, status). Cached for the duration of the process. #### `dm <user_id_or_name> <text>` Convenience: opens (or finds) a DM with the user and posts. `user_id_or_name` accepts either `U01ABC...` or `@alice` (resolved via users.list). ### Common recipes **"Extract Brain Notes from Slack (`🧠` Emoji)"** If Justin reacts to a message with the `🧠` (brain) emoji, use `scripts/fetch_slack_brains.py` to fetch the message and its surrounding context. 1. List all new reacted messages: ```bash python3 ${HERMES_HOME:-$HOME/.hermes}/skills/social-media/slack/scripts/fetch_slack_brains.py --list-new ``` 2. For each new item: - Generate a summarized Slack log in `Inputs/Slack/YYYY-MM-DD-slug.md`. - Do NOT append to the daily note (as the Notepad section is retired). - Mark the item as processed so it is never duplicated: ```bash python3 ${HERMES_HOME:-$HOME/.hermes}/skills/social-media/slack/scripts/fetch_slack_brains.py --mark-processed <channel_id> <ts> ``` **"What did I miss in #foo today?"** 1. `slack channels --type public --member-only` to find the channel ID. 2. `slack read <id> --since 24h --text`. 3. Summarize for Justin in Telegram. If you cite specific messages, include the permalink so he can jump. **"Did anyone @ me?"** 1. `slack search '@<justin_user_id>' --limit 30`. Or, more reliably: 2. Iterate Justin's member-channels, fetch recent messages, filter for `<@U_justin>` in text. The search endpoint sometimes misses mentions in private channels depending on indexing. **"What do I have reminders set on?"** Use `is:saved` (to find starred/saved messages) and `has:reminder` (as a fallback) in a search query — this is the supported workaround for the missing `reminders:read` scope (see Pitfalls). It returns messages Justin has saved or flagged with Slack's native reminder feature: ```bash slack search '(has:reminder OR is:saved) after:2026-05-19' --limit 50 ``` Note: the literal word "reminder" appearing in message *text* can occasionally bleed in via `has:reminder` (e.g. "Reminder: meeting at 3pm"). Use channel context to distinguish these from genuinely reminder-flagged or saved messages. **"Reply to Alice's DM"** 1. `slack channels --type im` to find the DM channel ID for Alice's user_id. 2. `slack read <dm_id> --since 24h --text` to read context. 3. Confirm content with Justin in Telegram (paraphrase his ask). 4. `slack post <dm_id> "<message>"`. ### Find the discussion about $TOPIC 1. `slack search '$TOPIC' --limit 20`. 2. Pick the relevant thread, grab `channel` and `thread_ts` (use `ts` if no thread_ts). 3. `slack thread <channel> <thread_ts> --text` for full context. ## The Brain Note-Taking System (🧠) You can capture high-quality conversation logs directly from Slack into the Obsidian vault under `Inputs/Slack/` using the emoji `🧠` or via interactive prompts in the **Morning Briefing**. ### 1. Automated Log Capture (via `🧠` reaction) When Justin adds a `🧠` reaction to a message: - A cron job (`Slack Brain Note Capture`) runs every 2 hours using the `fetch_slack_brains.py` helper. - If Justin (user ID `U095LHMC4UW`) reacted with `🧠`, the script fetches the entire thread (if part of one) or an 11-message context window surrounding the message. - It synthesizes a Markdown log inside `/home/justin.guest/Developer/obsidian-vault/Inputs/Slack/YYYY-MM-DD-slug.md` with participants, a summary of who said what, and key decisions/takeaways. Do NOT include verbatim Slack messages; store only summaries with retrieval metadata. - It does NOT append to today's daily note (as the Notepad section is retired). - It marks the thread processed inside `~/.hermes/processed_slack_brains.json`. ### 2. Manual Candidate Suggestions (Morning Briefing Phase 5) If a conversation wasn't explicitly tagged, the Morning Briefing live-scans active Slack threads from the last 36 hours for note-worthy discussions: - Runs `python3 ~/.hermes/scripts/fetch_slack_brains.py --list-candidates`. - **Candidate filters:** Requires `>= 3` messages, `>= 2` unique human participants, Justin's active participation or mention, and **no existing `🧠` reaction** (to avoid double-processing). - Prompts Justin with numbered candidates. If approved, it synthesizes the log, saves it as an input, and marks it processed (without updating the daily note). ### 3. Managing the System Helper The background script lives at: - `~/.hermes/scripts/fetch_slack_brains.py` Its database of processed threads is at: - `~/.hermes/processed_slack_brains.json` To manually mark a thread as processed (for testing or debugging): ```bash python3 ~/.hermes/scripts/fetch_slack_brains.py --mark-processed <channel_id> <parent_ts> ``` ## Pitfalls Justin uses a `🧠` (brain) emoji to flag Slack conversations that should be preserved in his vault as logs under `Inputs/Slack/`. There is a dedicated cron job ("Slack Brain Note Capture") running every 2 hours that automates this workflow: 1. Runs `fetch_slack_brains.py` to fetch messages where Justin (`U095LHMC4UW`) added a `🧠` reaction. 2. Checks against the processed cache at `~/.hermes/processed_slack_brains.json`. 3. If new, retrieves the full conversation context (full thread replies if it's a thread; or a chronological 11-message context window around the reacted message if it's not). 4. Summarizes the discussion via the agent and writes it to `/home/justin.guest/Developer/obsidian-vault/Inputs/Slack/YYYY-MM-DD-slug.md`. Do NOT include verbatim Slack messages; store only summaries with retrieval metadata. 5. Marks the message as processed using `python3 fetch_slack_brains.py --mark-processed <channel_id> <ts>`. (Does NOT append to the daily note notepad, as that section is retired). To run the fetcher script manually to list new reactions: ```bash python3 ~/.hermes/scripts/fetch_slack_brains.py ``` ## Pitfalls 1. **Permalinks for users not in the channel resolve to a 404 in Slack.** If you cite a permalink from #private-channel to a Slack viewer not in that channel, they'll see "Channel not found." For Justin himself this is fine — he can see anything he can read. Watch out if you ever summarize for someone else. 2. **`search.messages` excludes some recently-edited or recently-posted messages** due to Slack's search index lag (~minutes). For "right now" queries on a specific channel, use `read --since 1h` instead of search. 3. **Thread `ts` vs message `ts`**. A reply has its own `ts` AND a `thread_ts` pointing to the parent. To reply IN a thread, you pass the parent's `ts` (which IS the thread_ts). To react TO a specific reply, you pass the reply's own `ts`. Read both fields from JSON output before choosing which one to pass downstream. 4. **Direct messages are channels too** — their IDs start with `D`. The `channels:history` scope does NOT cover DMs; `im:history` does. If a `read` against a `D...` ID returns `missing_scope`, the token wasn't installed with `im:history`. 5. **Posting in a channel you're not a member of fails by default** even with `chat:write`. You'd need `chat:write.public` (deliberately not granted). For now: `slack channels --member-only` to see where posting will work. 6. **Display name vs username vs real name.** `user.name` is the legacy username (often the email prefix); `user.profile.display_name` is what appears in the UI; `user.profile.real_name` is the full name. The wrapper prefers `display_name` > `real_name` > `name` when rendering. 7. **Rate limits**. User tokens are on Tier 2-3 rate limits (~20-50 requests/minute depending on method). For "scan everything Justin is in", batch and add a small `time.sleep(1)` between channel reads. 8. **`reminders:read` scope is not installed.** Calling `reminders.list` via the Slack SDK will fail with `missing_scope`. The installed token does not have this scope. Use `is:saved` (for saved/starred messages) and `has:reminder` in a search query as a workaround (see "What do I have reminders set on?" in Common recipes). To get the real scope, Justin would need to re-authorize the Slack app with `reminders:read` added. ## Adding a second workspace later When/if Justin adds Nous Research or another workspace, the design path is: 1. Create a second Slack app in that workspace, get a second `xoxp-` token. 2. Add `SLACK_USER_TOKEN_NOUS=xoxp-...` to `.env` (one env var per workspace, distinguished by suffix). 3. Patch `scripts/slack.py` to accept `--workspace signlab|nous` and route to the corresponding env var. Default workspace = SignLab. 4. Update this SKILL.md with the workspace list. Pattern mirrors `gws_multi.py` in the google-workspace skill. ## Where things live - **Token**: `~/.hermes/.env` → `SLACK_USER_TOKEN` - **Wrapper script**: `~/.hermes/skills/social-media/slack/scripts/slack.py` - **Reaction note capture script**: `~/.hermes/scripts/fetch_slack_brains.py` - **Reaction note processed cache**: `~/.hermes/processed_slack_brains.json` - **Convenience symlink** (optional): `~/.local/bin/slack` → wrapper
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기