Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ bundle exec rake ci # Full CI: syntax + rubocop + tests
bundle exec rake rubocop # Lint only
bundle exec rake lint # Syntax check + rubocop
bundle exec rake server # Start dev server
bundle exec rake config # Manage multi-project integrations (TUI)
DEBUG=true bundle exec ruby app.rb # Run with debug logging
```

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

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

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

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

## Environment Variables

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

## Code Style

Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ gem 'dotenv'
gem 'puma'
gem 'sinatra'
gem 'thin'
gem 'tty-prompt'
gem 'tty-table'

group :development do
gem 'rake'
Expand Down
27 changes: 27 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ GEM
daemons (1.4.1)
dotenv (3.1.8)
drb (2.2.3)
equatable (0.7.0)
eventmachine (1.2.7)
hashdiff (1.2.0)
json (2.13.2)
Expand All @@ -23,11 +24,14 @@ GEM
prism (~> 1.5)
mustermann (3.0.4)
ruby2_keywords (~> 0.0.1)
necromancer (0.7.0)
nio4r (2.7.4)
parallel (1.27.0)
parser (3.3.9.0)
ast (~> 2.4.1)
racc
pastel (0.8.0)
tty-color (~> 0.5)
prism (1.9.0)
public_suffix (6.0.2)
puma (7.0.1)
Expand Down Expand Up @@ -83,13 +87,34 @@ GEM
logger
rack (>= 1, < 4)
tilt (2.7.0)
tty-color (0.6.0)
tty-cursor (0.7.1)
tty-prompt (0.23.1)
pastel (~> 0.8)
tty-reader (~> 0.8)
tty-reader (0.9.0)
tty-cursor (~> 0.7)
tty-screen (~> 0.8)
wisper (~> 2.0)
tty-screen (0.8.2)
tty-table (0.3.0)
equatable (~> 0.5)
necromancer (~> 0.3)
pastel (~> 0.4)
tty-screen (~> 0.2)
unicode_utils (~> 1.4.0)
verse (~> 0.4)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.2.0)
unicode_utils (1.4.0)
verse (0.4.0)
unicode_utils (~> 1.4.0)
webmock (3.25.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
wisper (2.0.1)

PLATFORMS
arm64-darwin-24
Expand All @@ -106,6 +131,8 @@ DEPENDENCIES
rubocop-rake
sinatra
thin
tty-prompt
tty-table
webmock

BUNDLED WITH
Expand Down
93 changes: 90 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Ready to get started? Here's the fastest way to set up slack-github-threads:
- 🔒 **Secure**: Uses environment variables for sensitive tokens
- 🎯 **Smart Formatting**: Preserves message structure, usernames, and timestamps
- 📱 **Multiple Interfaces**: Slash commands, message shortcuts, and global shortcuts
- 🏢 **Multi-Project Support**: Connect multiple Slack workspaces to different GitHub repositories with encrypted configuration

### Why Use This Tool?

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

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

## Multi-Project Setup

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.

### How It Works

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.

This means existing single-project deployments require **no changes** -- they continue to work with just environment variables.

### Managing Projects

Use the interactive TUI (terminal user interface) to add, edit, remove, and list project integrations:

```bash
bundle exec rake config
# or directly:
bundle exec ruby bin/slack-gh-config
```

The TUI will guide you through:

1. **Setting a passphrase** (first run) or **entering your passphrase** (subsequent runs)
2. **Main menu** with options to:
- **Add project** -- provide a name, Slack team ID, Slack bot token, GitHub token, and optional default GitHub org
- **Edit project** -- update any field of an existing project
- **Remove project** -- delete a project configuration
- **List projects** -- display all configured projects in a table (tokens are masked)

### Finding Your Slack Team ID

Your Slack team ID (e.g., `T12345ABC`) can be found in:
- The URL when viewing your workspace in a browser: `https://app.slack.com/client/T12345ABC/...`
- Slack Admin Settings > About this workspace

### Running the App in Multi-Project Mode

Set the `CONFIG_PASSPHRASE` environment variable so the app can decrypt the configuration on startup:

```bash
CONFIG_PASSPHRASE=your-passphrase bundle exec ruby app.rb
```

Or add it to your `.env` file:

```env
CONFIG_PASSPHRASE=your-passphrase
```

### Encrypted Configuration

The project configuration is stored at `.config/projects.enc`, which is:

- **Encrypted at rest** using AES-256-GCM with a passphrase-derived key (PBKDF2, 100,000 iterations)
- **Git-ignored** by default (`.config/` is in `.gitignore`)
- **Authenticated** -- any tampering with the file is detected automatically

### Docker and Kamal Deployments

For containerized deployments, you need to:

1. Mount or copy the `.config/projects.enc` file into the container
2. Set `CONFIG_PASSPHRASE` as an environment variable or secret

Example with Docker:

```bash
docker run -p 3000:3000 \
-v $(pwd)/.config:/app/.config \
-e CONFIG_PASSPHRASE=your-passphrase \
--env-file .env \
slack-github-threads
```

## Usage

### Local Development
Expand Down Expand Up @@ -244,9 +320,16 @@ docker run -p 3000:3000 --env-file .env slack-github-threads
├── Gemfile # Ruby dependencies
├── Dockerfile # Docker configuration
├── Rakefile # Task definitions and test runner
├── bin/
│ └── slack-gh-config # TUI for managing multi-project config
├── docs/ # Documentation and configuration
│ └── app-manifest.json # Slack app manifest for easy setup
├── lib/ # Application modules
│ ├── cli/
│ │ └── tui.rb # Interactive TUI for project management
│ ├── config/
│ │ ├── encryption.rb # AES-256-GCM encryption utilities
│ │ └── project_config.rb # Multi-project configuration model
│ ├── services/ # Business logic services
│ │ ├── slack_service.rb # Slack API interactions
│ │ ├── github_service.rb # GitHub API interactions
Expand All @@ -257,10 +340,12 @@ docker run -p 3000:3000 --env-file .env slack-github-threads
├── test/ # Test suite
│ ├── test_helper.rb # Test configuration and helpers
│ ├── test_app.rb # Integration tests
│ ├── test_multi_project.rb # Multi-project routing tests
│ ├── cli/ # CLI/TUI tests
│ ├── config/ # Encryption and config tests
│ └── services/ # Service unit tests
│ ├── test_slack_service.rb
│ ├── test_github_service.rb
│ └── test_text_processor.rb
├── .config/ # Encrypted project config (git-ignored)
│ └── projects.enc # AES-256-GCM encrypted YAML
├── config/
│ └── deploy.yml.example # Kamal deployment configuration template
└── .kamal/
Expand Down Expand Up @@ -419,6 +504,8 @@ See [docs/CONVENTIONAL_COMMITS.md](docs/CONVENTIONAL_COMMITS.md) for detailed co
- Use environment variables for all sensitive data
- Regularly rotate your API tokens
- Use HTTPS in production
- Multi-project config is encrypted at rest with AES-256-GCM; the `.config/` directory is git-ignored
- Choose a strong passphrase for your config encryption; store `CONFIG_PASSPHRASE` securely in your deployment secrets

## License

Expand Down
6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ task :server do
system('bundle exec ruby app.rb')
end

# Configure multi-project integrations
desc 'Manage multi-project Slack/GitHub integrations'
task :config do
system('bundle exec ruby bin/slack-gh-config')
end

# Check syntax of all Ruby files
desc 'Check syntax of all Ruby files'
task :syntax do
Expand Down
Loading
Loading