← back to Slack To Steve

README.md

52 lines

# slack-to-steve — two-way #claude-to-steve pipe

Lets Steve talk to Claude **inline** in the DW Slack workspace (`designerwallc-hyg8426`,
channel `#claude-to-steve`). Claude reads new messages Steve posts, acks in-thread, and
(when armed) actions them and replies. The Slack analog of `george-reply-ingest` (email).

**Decision of record (DTD 2026-07-12, verdict A, unanimous Claude+Codex):** commit to full
two-way; gate only what Claude can't self-grant (the read scope + reinstall + channel create);
pre-build the poller so it's one-switch-live. This repo is that pre-build — **un-armed**.

## Current status: BLOCKED on Steve (read scope)
The Slack bot token today has `chat:write`, `chat:write.public`, `channels:read` —
it can **post** but **cannot read** messages (no `channels:history`) and **cannot create**
channels (`channels:manage`). So the pipe can't go live until the steps below.

## Go-live — 4 steps (only Steve can do 1–3)
1. **Add the read scope.** Slack app config → *OAuth & Permissions* → Bot Token Scopes →
   add **`channels:history`** (and `groups:history` if `#claude-to-steve` will be **private**).
   Optional: **`channels:manage`** if you want Claude to auto-create channels.
2. **Reinstall the app** to the workspace (the OAuth re-consent — your click). This mints a
   new bot token with the added scope. If the token value changes, update `SLACK_BOT_TOKEN`
   in `~/.claude.json` (slack MCP env) **and** this project's `.env`.
3. **Create `#claude-to-steve`** in Slack and **invite the bot** (`/invite @<botname>`).
   (Or, once `channels:manage` is added, Claude can create it.)
4. **Wire + verify** (Claude does this):
   ```sh
   cd ~/Projects/slack-to-steve
   cp .env.example .env            # paste SLACK_BOT_TOKEN
   node whoami.mjs                 # prints SLACK_CHANNEL_ID; also how to get STEVE_USER_ID
   #  → fill SLACK_CHANNEL_ID + STEVE_USER_ID in .env
   node preflight.mjs             # must print ✅ READY
   node ingest.mjs                # one manual poll — post a test msg in the channel first
   ```
   When `ingest.mjs` acks your test message in-thread, install the launchd poller:
   ```sh
   cp com.steve.slack-to-steve-ingest.plist.template ~/Library/LaunchAgents/com.steve.slack-to-steve-ingest.plist
   launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.steve.slack-to-steve-ingest.plist
   ```

## Rails (do not bypass)
- **`AUTO_EXECUTE=0` by default** — captures + acks + queues to `data/inbox.jsonl`; a live
  session actions the queue. Flip to `1` (auto-run each message via `claude -p`) **only** on
  Steve's explicit approval — autonomous execution from an inbound channel is itself gated.
- **Sender allowlist** — only `STEVE_USER_ID` messages are ever processed.
- Cursor (`data/last-ts.txt`) guarantees no message is actioned twice.

## Files
- `preflight.mjs` — READY/BLOCKED check (has the read scope landed?)
- `ingest.mjs` — the poller (read → verify → ack → queue → [armed] execute → reply)
- `whoami.mjs` — resolve channel + user IDs for `.env`
- `com.steve.slack-to-steve-ingest.plist.template` — launchd job (90s), **not installed**