Skip to content

Commit 2086765

Browse files
isaacrowntreeclaude
andcommitted
fix(send): unescape literal \n and \t in CLI arguments
Literal \n sequences passed as CLI args were sent to Slack as-is, rendering poorly. Now unescapeShellArtifacts converts \n→newline and \t→tab, with a placeholder to protect escaped backslashes (\\). Also adds team setup script, docs, and Makefile setup target. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fa768a5 commit 2086765

7 files changed

Lines changed: 420 additions & 14 deletions

File tree

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ install-skill:
3939
@mkdir -p $(HOME)/.claude/skills
4040
@ln -sfn $(CURDIR)/skills/slackbuzz-cli $(HOME)/.claude/skills/slackbuzz-cli
4141
@echo "Linked Claude Code skill: slackbuzz-cli → ~/.claude/skills/slackbuzz-cli"
42-
@echo ""
43-
@echo "Or install via plugin marketplace (no clone required):"
44-
@echo " /plugin marketplace add triptechtravel/slackbuzz-cli"
45-
@echo " /plugin install slackbuzz-cli@slackbuzz-cli"
42+
43+
.PHONY: setup
44+
setup:
45+
@bash scripts/setup.sh

docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default defineConfig({
3333
items: [
3434
{ label: 'Installation', slug: 'installation' },
3535
{ label: 'Getting Started', slug: 'getting-started' },
36+
{ label: 'Team Setup', slug: 'team-setup' },
3637
],
3738
},
3839
{

docs/src/content/docs/ai-agents.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,24 +140,35 @@ slackbuzz later list --json
140140
slackbuzz threads --since 1d --json
141141
```
142142

143-
## Claude Code skill (plugin marketplace)
143+
## Claude Code skill
144144

145-
Install the SlackBuzz CLI skill into Claude Code so it automatically uses the CLI for Slack operations. No need to clone this repo.
145+
Install the SlackBuzz CLI skill into Claude Code so it automatically uses the CLI for Slack operations.
146+
147+
### Quick install (no clone required)
146148

147149
```sh
148-
# In Claude Code, run:
149-
/plugin marketplace add triptechtravel/slackbuzz-cli
150-
/plugin install slackbuzz-cli@slackbuzz-cli
150+
mkdir -p ~/.claude/skills/slackbuzz-cli
151+
curl -fsSL https://raw.githubusercontent.com/triptechtravel/slackbuzz-cli/main/skills/slackbuzz-cli/SKILL.md \
152+
-o ~/.claude/skills/slackbuzz-cli/SKILL.md
151153
```
152154

153-
Once installed, Claude Code will automatically use `slackbuzz` commands when you ask it to send messages, check activity, search Slack, manage reactions, or set your status.
155+
### From a local clone
154156

155-
If you have the repo cloned locally, you can alternatively symlink the skill:
157+
If you have the repo checked out, symlink the skill so it stays up to date with `git pull`:
156158

157159
```sh
158160
make install-skill
159161
```
160162

163+
### What the skill does
164+
165+
Once installed, Claude Code will automatically use `slackbuzz` commands when you ask it to interact with Slack. You can use natural language instead of remembering CLI flags:
166+
167+
- "Check my Slack inbox"
168+
- "Send @alice a message about the deployment"
169+
- "Search Slack for the API spec discussion"
170+
- "Set my status to deep work for 2 hours"
171+
161172
### @mention resolution
162173

163174
`@name` mentions in message bodies are resolved automatically to Slack's `<@USERID>` format before posting. Agents can write natural `@username` mentions without manually looking up user IDs:
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
title: Team Setup
3+
description: Get your team up and running with slackbuzz in one command.
4+
---
5+
6+
# Team setup
7+
8+
Get a teammate from zero to fully operational with a single command.
9+
10+
## One-line setup
11+
12+
```sh
13+
curl -sL https://raw.githubusercontent.com/triptechtravel/slackbuzz-cli/main/scripts/setup.sh | bash
14+
```
15+
16+
The script walks through each step interactively: installing slackbuzz, setting up the Claude Code skill, and authenticating with Slack. It's idempotent — safe to run again if anything changes.
17+
18+
If you have the repo cloned locally, you can also run:
19+
20+
```sh
21+
make setup
22+
```
23+
24+
## What you'll need
25+
26+
| Prerequisite | Required? | Notes |
27+
|---|---|---|
28+
| **Homebrew** | Yes | The script installs slackbuzz via `brew`. Install from [brew.sh](https://brew.sh). |
29+
| **Bot token** (`xoxb-...`) | Yes | Shared across the team. Your team lead will provide this. |
30+
| **User token** (`xoxp-...`) | Yes | Unique per person. Each team member generates their own. |
31+
| **Claude Code** | Optional | If installed, the script adds the slackbuzz skill automatically. |
32+
33+
## Understanding the tokens
34+
35+
slackbuzz uses two Slack tokens. The CLI selects the right one automatically — you never need to specify which to use.
36+
37+
**Bot token** (`xoxb-...`) — shared across the team. One app installation covers everyone. Used for reading channels, listing users, and reactions.
38+
39+
**User token** (`xoxp-...`) — unique per person. Each team member installs the Slack app to their own account. Used for sending messages as you, search, DMs, and status.
40+
41+
Both tokens come from the same Slack app. The bot token is the same for everyone on the team; the user token is different per person.
42+
43+
## Manual step-by-step
44+
45+
If you prefer not to use the setup script, here's what it does:
46+
47+
### 1. Install slackbuzz
48+
49+
```sh
50+
brew install triptechtravel/tap/slackbuzz
51+
```
52+
53+
### 2. Install the Claude Code skill (optional)
54+
55+
```sh
56+
mkdir -p ~/.claude/skills/slackbuzz-cli
57+
curl -fsSL https://raw.githubusercontent.com/triptechtravel/slackbuzz-cli/main/skills/slackbuzz-cli/SKILL.md \
58+
-o ~/.claude/skills/slackbuzz-cli/SKILL.md
59+
```
60+
61+
Or from a local clone:
62+
63+
```sh
64+
make install-skill
65+
```
66+
67+
### 3. Authenticate
68+
69+
```sh
70+
slackbuzz auth login --bot-token
71+
slackbuzz auth login --user-token
72+
```
73+
74+
Paste each token when prompted. Get them from your Slack app's **OAuth & Permissions** page.
75+
76+
### 4. Verify
77+
78+
```sh
79+
slackbuzz doctor
80+
```
81+
82+
## For team leads: creating the Slack app
83+
84+
Before your team can authenticate, someone needs to create the Slack app and install it to the workspace.
85+
86+
### Create the app
87+
88+
The fastest way is to use the built-in command:
89+
90+
```sh
91+
slackbuzz app create
92+
```
93+
94+
This creates a Slack app pre-configured with all required scopes for both token types, opens the install page in your browser, and prompts you to paste the OAuth tokens.
95+
96+
### Share with your team
97+
98+
After the app is installed to your workspace:
99+
100+
1. Go to [api.slack.com/apps](https://api.slack.com/apps) and select the app
101+
2. Navigate to **OAuth & Permissions**
102+
3. Copy the **Bot User OAuth Token** (`xoxb-...`) — this is the same for everyone
103+
4. Share the bot token with your team (e.g., in a private Slack channel or password manager)
104+
5. Each team member visits the same OAuth page and copies their own **User OAuth Token** (`xoxp-...`)
105+
106+
Then each team member runs the setup script and pastes both tokens when prompted.
107+
108+
### Required scopes
109+
110+
The `slackbuzz app create` command sets these up automatically. If creating the app manually:
111+
112+
**Bot token (`xoxb-`):** `chat:write`, `channels:history`, `channels:read`, `emoji:read`, `groups:history`, `groups:read`, `im:history`, `im:read`, `im:write`, `mpim:history`, `mpim:read`, `reactions:read`, `reactions:write`, `users:read`
113+
114+
**User token (`xoxp-`):** `channels:read`, `chat:write`, `groups:read`, `im:read`, `im:write`, `mpim:read`, `search:read`, `stars:read`, `stars:write`, `users:read`, `users.profile:read`, `users.profile:write`
115+
116+
## Troubleshooting
117+
118+
### "Not authenticated" errors
119+
120+
Re-run authentication for the missing token:
121+
122+
```sh
123+
slackbuzz auth status # See which tokens are configured
124+
slackbuzz auth login --bot-token # Re-enter bot token
125+
slackbuzz auth login --user-token # Re-enter user token
126+
```
127+
128+
### Scope errors ("missing_scope")
129+
130+
The Slack app needs additional OAuth scopes. Ask your team lead to:
131+
132+
1. Go to [api.slack.com/apps](https://api.slack.com/apps) → your app → **OAuth & Permissions**
133+
2. Add the missing scope listed in the error message
134+
3. Reinstall the app to the workspace
135+
4. Team members re-run `slackbuzz auth login` with the new tokens
136+
137+
### Updating slackbuzz
138+
139+
```sh
140+
brew update && brew upgrade triptechtravel/tap/slackbuzz
141+
```
142+
143+
Or re-run the setup script — it detects existing installations and upgrades automatically.
144+
145+
### Updating the Claude Code skill
146+
147+
```sh
148+
curl -fsSL https://raw.githubusercontent.com/triptechtravel/slackbuzz-cli/main/skills/slackbuzz-cli/SKILL.md \
149+
-o ~/.claude/skills/slackbuzz-cli/SKILL.md
150+
```
151+
152+
If you installed via `make install-skill` (symlink), just `git pull` in the repo.
153+
154+
### Uninstalling
155+
156+
```sh
157+
brew uninstall slackbuzz # Remove the CLI
158+
rm -rf ~/.claude/skills/slackbuzz-cli # Remove the Claude Code skill
159+
slackbuzz auth logout # Remove stored tokens (run before uninstall)
160+
```

pkg/cmd/message/send.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,17 @@ func resolveTargetUserID(r *api.Resolver, target string) string {
251251

252252
// unescapeShellArtifacts removes common shell escape sequences that leak into
253253
// CLI arguments. For example, zsh's history expansion escapes ! as \! when
254-
// passed through double-quoted strings.
254+
// passed through double-quoted strings, and literal \n sequences appear when
255+
// users include newlines in quoted arguments.
255256
func unescapeShellArtifacts(text string) string {
257+
// Protect escaped backslashes (\\) before processing other sequences
258+
const placeholder = "\x00BACKSLASH\x00"
259+
text = strings.ReplaceAll(text, `\\`, placeholder)
256260
text = strings.ReplaceAll(text, `\!`, "!")
257261
text = strings.ReplaceAll(text, `\?`, "?")
262+
text = strings.ReplaceAll(text, `\n`, "\n")
263+
text = strings.ReplaceAll(text, `\t`, "\t")
264+
text = strings.ReplaceAll(text, placeholder, `\`)
258265
return text
259266
}
260267

pkg/cmd/message/send_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ func TestUnescapeShellArtifacts(t *testing.T) {
2929
want: "Hello world",
3030
},
3131
{
32-
name: "legitimate backslash preserved",
32+
name: "escaped backslash collapses",
3333
text: `path\\to\\file`,
34-
want: `path\\to\\file`,
34+
want: `path\to\file`,
3535
},
3636
{
3737
name: "mixed content",
@@ -48,6 +48,21 @@ func TestUnescapeShellArtifacts(t *testing.T) {
4848
text: `\!`,
4949
want: "!",
5050
},
51+
{
52+
name: "literal newlines",
53+
text: `line one\n\nline two`,
54+
want: "line one\n\nline two",
55+
},
56+
{
57+
name: "literal tab",
58+
text: `col1\tcol2`,
59+
want: "col1\tcol2",
60+
},
61+
{
62+
name: "mixed escapes with newlines",
63+
text: `Build passed\!\n\nDetails at link`,
64+
want: "Build passed!\n\nDetails at link",
65+
},
5166
}
5267

5368
for _, tt := range tests {

0 commit comments

Comments
 (0)