Skip to content

Commit ef89f98

Browse files
markhallenclaude
andauthored
feat: support multiple Slack/GitHub project integrations with TUI (#38)
* feat: support multiple Slack/GitHub project integrations with TUI configuration Add multi-project support allowing multiple Slack workspaces to be paired with different GitHub repositories. Projects are managed via an interactive TUI (tty-prompt) and stored in an AES-256-GCM encrypted config file. Incoming requests are routed by Slack team_id with env var fallback for full backward compatibility. Closes #34 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback - Log warning when config file exists but CONFIG_PASSPHRASE is unset - Rescue ArgumentError in decrypt for corrupted/non-Base64 data - Add blob length validation before attempting decryption - Enforce unique slack_team_id in add_project and validate on update - Require non-empty passphrases in TUI prompts - Replace send() with explicit lambda dispatch table in menu loop - Add required: true to team_id field in edit flow - Show restart reminder when exiting TUI with configured projects Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4782860 commit ef89f98

15 files changed

Lines changed: 1123 additions & 40 deletions

CLAUDE.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ bundle exec rake ci # Full CI: syntax + rubocop + tests
1717
bundle exec rake rubocop # Lint only
1818
bundle exec rake lint # Syntax check + rubocop
1919
bundle exec rake server # Start dev server
20+
bundle exec rake config # Manage multi-project integrations (TUI)
2021
DEBUG=true bundle exec ruby app.rb # Run with debug logging
2122
```
2223

@@ -27,19 +28,24 @@ To run a single test file: `bundle exec ruby test/services/test_text_processor.r
2728
```
2829
Slack (slash command / shortcut)
2930
→ app.rb (Sinatra routing, 3 endpoints: GET /up, POST /ghcomment, POST /shortcut)
31+
→ resolve_tokens(team_id) → ProjectConfig lookup or ENV fallback
3032
→ CommentService (orchestration)
3133
├→ SlackService (fetch thread via conversations.replies, resolve user mentions)
3234
├→ TextProcessor (format messages: HTML entity decoding, @mention replacement)
3335
└→ GitHubService (POST comment to issue via REST API)
3436
```
3537

36-
- **app.rb** — Entry point. Routes requests, validates params, delegates to CommentService.
38+
- **app.rb** — Entry point. Routes requests, resolves credentials by team_id, delegates to CommentService.
39+
- **lib/config/encryption.rb** — AES-256-GCM encryption/decryption with PBKDF2 key derivation (stdlib only).
40+
- **lib/config/project_config.rb** — Multi-project config model: CRUD, encrypted file I/O, team_id lookup.
41+
- **lib/cli/tui.rb** — Interactive TUI (tty-prompt) for managing project integrations.
3742
- **lib/services/comment_service.rb** — Orchestrates the flow: fetch thread → format → post to GitHub → reply in Slack.
3843
- **lib/services/slack_service.rb** — Slack API client (Net::HTTP). Auto-joins channels if bot isn't a member.
3944
- **lib/services/github_service.rb** — GitHub API client (Net::HTTP). Parses issue URLs to extract org/repo/number.
4045
- **lib/services/text_processor.rb** — Converts Slack message formatting to GitHub-compatible markdown.
4146
- **lib/helpers/modal_builder.rb** — Constructs Slack Block Kit modals for shortcut flows.
4247
- **lib/version_helper.rb** — Semantic versioning and changelog generation from conventional commits.
48+
- **bin/slack-gh-config** — CLI entry point for the project configuration TUI.
4349

4450
All API calls use Ruby's native `Net::HTTP` — no external HTTP client gems.
4551

@@ -52,8 +58,8 @@ All API calls use Ruby's native `Net::HTTP` — no external HTTP client gems.
5258

5359
## Environment Variables
5460

55-
Required: `SLACK_BOT_TOKEN` (xoxb_*), `GITHUB_TOKEN` (ghp_*)
56-
Optional: `DEBUG` (true/false), `RACK_ENV`, `PORT` (default 3000)
61+
Required (single-project mode): `SLACK_BOT_TOKEN` (xoxb-*), `GITHUB_TOKEN` (ghp_*)
62+
Optional: `DEBUG` (true/false), `RACK_ENV`, `PORT` (default 3000), `CONFIG_PASSPHRASE` (for multi-project mode)
5763

5864
## Code Style
5965

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ gem 'dotenv'
66
gem 'puma'
77
gem 'sinatra'
88
gem 'thin'
9+
gem 'tty-prompt'
10+
gem 'tty-table'
911

1012
group :development do
1113
gem 'rake'

Gemfile.lock

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ GEM
1212
daemons (1.4.1)
1313
dotenv (3.1.8)
1414
drb (2.2.3)
15+
equatable (0.7.0)
1516
eventmachine (1.2.7)
1617
hashdiff (1.2.0)
1718
json (2.13.2)
@@ -23,11 +24,14 @@ GEM
2324
prism (~> 1.5)
2425
mustermann (3.0.4)
2526
ruby2_keywords (~> 0.0.1)
27+
necromancer (0.7.0)
2628
nio4r (2.7.4)
2729
parallel (1.27.0)
2830
parser (3.3.9.0)
2931
ast (~> 2.4.1)
3032
racc
33+
pastel (0.8.0)
34+
tty-color (~> 0.5)
3135
prism (1.9.0)
3236
public_suffix (6.0.2)
3337
puma (7.0.1)
@@ -83,13 +87,34 @@ GEM
8387
logger
8488
rack (>= 1, < 4)
8589
tilt (2.7.0)
90+
tty-color (0.6.0)
91+
tty-cursor (0.7.1)
92+
tty-prompt (0.23.1)
93+
pastel (~> 0.8)
94+
tty-reader (~> 0.8)
95+
tty-reader (0.9.0)
96+
tty-cursor (~> 0.7)
97+
tty-screen (~> 0.8)
98+
wisper (~> 2.0)
99+
tty-screen (0.8.2)
100+
tty-table (0.3.0)
101+
equatable (~> 0.5)
102+
necromancer (~> 0.3)
103+
pastel (~> 0.4)
104+
tty-screen (~> 0.2)
105+
unicode_utils (~> 1.4.0)
106+
verse (~> 0.4)
86107
unicode-display_width (3.1.4)
87108
unicode-emoji (~> 4.0, >= 4.0.4)
88109
unicode-emoji (4.2.0)
110+
unicode_utils (1.4.0)
111+
verse (0.4.0)
112+
unicode_utils (~> 1.4.0)
89113
webmock (3.25.1)
90114
addressable (>= 2.8.0)
91115
crack (>= 0.3.2)
92116
hashdiff (>= 0.4.0, < 2.0.0)
117+
wisper (2.0.1)
93118

94119
PLATFORMS
95120
arm64-darwin-24
@@ -106,6 +131,8 @@ DEPENDENCIES
106131
rubocop-rake
107132
sinatra
108133
thin
134+
tty-prompt
135+
tty-table
109136
webmock
110137

111138
BUNDLED WITH

README.md

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Ready to get started? Here's the fastest way to set up slack-github-threads:
4545
- 🔒 **Secure**: Uses environment variables for sensitive tokens
4646
- 🎯 **Smart Formatting**: Preserves message structure, usernames, and timestamps
4747
- 📱 **Multiple Interfaces**: Slash commands, message shortcuts, and global shortcuts
48+
- 🏢 **Multi-Project Support**: Connect multiple Slack workspaces to different GitHub repositories with encrypted configuration
4849

4950
### Why Use This Tool?
5051

@@ -101,6 +102,7 @@ Create a `.env` file with the following variables:
101102
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
102103
GITHUB_TOKEN=ghp_your-github-personal-access-token
103104
DEBUG=false # Optional: set to 'true' for debug logging
105+
CONFIG_PASSPHRASE= # Optional: passphrase to decrypt multi-project config (see Multi-Project Setup)
104106
```
105107

106108
### Getting Tokens
@@ -155,6 +157,80 @@ You have two options for setting up your Slack app:
155157
- `repo` (for private repositories) or `public_repo` (for public repositories only)
156158
3. Copy the generated token
157159

160+
## Multi-Project Setup
161+
162+
The app supports connecting multiple Slack workspaces to different GitHub repositories. Each workspace-repository pair is stored as a "project" in an encrypted configuration file.
163+
164+
### How It Works
165+
166+
When the app receives a request from Slack, it extracts the workspace's `team_id` from the payload and looks up the matching project configuration to find the correct Slack bot token and GitHub token. If no match is found, it falls back to the `SLACK_BOT_TOKEN` and `GITHUB_TOKEN` environment variables.
167+
168+
This means existing single-project deployments require **no changes** -- they continue to work with just environment variables.
169+
170+
### Managing Projects
171+
172+
Use the interactive TUI (terminal user interface) to add, edit, remove, and list project integrations:
173+
174+
```bash
175+
bundle exec rake config
176+
# or directly:
177+
bundle exec ruby bin/slack-gh-config
178+
```
179+
180+
The TUI will guide you through:
181+
182+
1. **Setting a passphrase** (first run) or **entering your passphrase** (subsequent runs)
183+
2. **Main menu** with options to:
184+
- **Add project** -- provide a name, Slack team ID, Slack bot token, GitHub token, and optional default GitHub org
185+
- **Edit project** -- update any field of an existing project
186+
- **Remove project** -- delete a project configuration
187+
- **List projects** -- display all configured projects in a table (tokens are masked)
188+
189+
### Finding Your Slack Team ID
190+
191+
Your Slack team ID (e.g., `T12345ABC`) can be found in:
192+
- The URL when viewing your workspace in a browser: `https://app.slack.com/client/T12345ABC/...`
193+
- Slack Admin Settings > About this workspace
194+
195+
### Running the App in Multi-Project Mode
196+
197+
Set the `CONFIG_PASSPHRASE` environment variable so the app can decrypt the configuration on startup:
198+
199+
```bash
200+
CONFIG_PASSPHRASE=your-passphrase bundle exec ruby app.rb
201+
```
202+
203+
Or add it to your `.env` file:
204+
205+
```env
206+
CONFIG_PASSPHRASE=your-passphrase
207+
```
208+
209+
### Encrypted Configuration
210+
211+
The project configuration is stored at `.config/projects.enc`, which is:
212+
213+
- **Encrypted at rest** using AES-256-GCM with a passphrase-derived key (PBKDF2, 100,000 iterations)
214+
- **Git-ignored** by default (`.config/` is in `.gitignore`)
215+
- **Authenticated** -- any tampering with the file is detected automatically
216+
217+
### Docker and Kamal Deployments
218+
219+
For containerized deployments, you need to:
220+
221+
1. Mount or copy the `.config/projects.enc` file into the container
222+
2. Set `CONFIG_PASSPHRASE` as an environment variable or secret
223+
224+
Example with Docker:
225+
226+
```bash
227+
docker run -p 3000:3000 \
228+
-v $(pwd)/.config:/app/.config \
229+
-e CONFIG_PASSPHRASE=your-passphrase \
230+
--env-file .env \
231+
slack-github-threads
232+
```
233+
158234
## Usage
159235

160236
### Local Development
@@ -244,9 +320,16 @@ docker run -p 3000:3000 --env-file .env slack-github-threads
244320
├── Gemfile # Ruby dependencies
245321
├── Dockerfile # Docker configuration
246322
├── Rakefile # Task definitions and test runner
323+
├── bin/
324+
│ └── slack-gh-config # TUI for managing multi-project config
247325
├── docs/ # Documentation and configuration
248326
│ └── app-manifest.json # Slack app manifest for easy setup
249327
├── lib/ # Application modules
328+
│ ├── cli/
329+
│ │ └── tui.rb # Interactive TUI for project management
330+
│ ├── config/
331+
│ │ ├── encryption.rb # AES-256-GCM encryption utilities
332+
│ │ └── project_config.rb # Multi-project configuration model
250333
│ ├── services/ # Business logic services
251334
│ │ ├── slack_service.rb # Slack API interactions
252335
│ │ ├── github_service.rb # GitHub API interactions
@@ -257,10 +340,12 @@ docker run -p 3000:3000 --env-file .env slack-github-threads
257340
├── test/ # Test suite
258341
│ ├── test_helper.rb # Test configuration and helpers
259342
│ ├── test_app.rb # Integration tests
343+
│ ├── test_multi_project.rb # Multi-project routing tests
344+
│ ├── cli/ # CLI/TUI tests
345+
│ ├── config/ # Encryption and config tests
260346
│ └── services/ # Service unit tests
261-
│ ├── test_slack_service.rb
262-
│ ├── test_github_service.rb
263-
│ └── test_text_processor.rb
347+
├── .config/ # Encrypted project config (git-ignored)
348+
│ └── projects.enc # AES-256-GCM encrypted YAML
264349
├── config/
265350
│ └── deploy.yml.example # Kamal deployment configuration template
266351
└── .kamal/
@@ -419,6 +504,8 @@ See [docs/CONVENTIONAL_COMMITS.md](docs/CONVENTIONAL_COMMITS.md) for detailed co
419504
- Use environment variables for all sensitive data
420505
- Regularly rotate your API tokens
421506
- Use HTTPS in production
507+
- Multi-project config is encrypted at rest with AES-256-GCM; the `.config/` directory is git-ignored
508+
- Choose a strong passphrase for your config encryption; store `CONFIG_PASSPHRASE` securely in your deployment secrets
422509

423510
## License
424511

Rakefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ task :server do
8181
system('bundle exec ruby app.rb')
8282
end
8383

84+
# Configure multi-project integrations
85+
desc 'Manage multi-project Slack/GitHub integrations'
86+
task :config do
87+
system('bundle exec ruby bin/slack-gh-config')
88+
end
89+
8490
# Check syntax of all Ruby files
8591
desc 'Check syntax of all Ruby files'
8692
task :syntax do

0 commit comments

Comments
 (0)