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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ slack send-message alex_collins@intuit.com "**Bold**, *italic*, ~~strikethrough~
Messages automatically convert Markdown to Slack's Mrkdwn format. Supported features:

- **Bold**: `**text**` or `__text__` → `*text*`
- **Italic**: `*text*` → `_text_`
- **Italic**: `*text*` or `_text_` → `_text_` (single underscores already in Mrkdwn format)
- **Strikethrough**: `~~text~~` → `~text~`
- **Inline code**: `` `code` `` (unchanged)
- **Links**: `[text](url)` → `<url|text>`
Expand Down
20 changes: 10 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func run(ctx context.Context, args []string) error {
if len(args) < 3 {
return fmt.Errorf("usage: slack send-message <channel|email> <message> [thread-ts]")
}

token := getToken()
if token == "" {
return fmt.Errorf("Slack token must be set (use 'slack configure' or set SLACK_TOKEN env var)")
Expand All @@ -65,13 +65,13 @@ func run(ctx context.Context, args []string) error {
// disable HTTP/2 support as it causes issues with some proxies
http.DefaultTransport.(*http.Transport).ForceAttemptHTTP2 = false
api := slack.New(token)

// Check if optional thread-ts parameter is provided for replying to a thread
var timestamp string
if len(args) >= 4 {
timestamp = args[3]
}

_, err := sendMessage(ctx, api, args[1], args[2], timestamp)
return err
default:
Expand All @@ -84,12 +84,12 @@ func getToken() string {
if token := os.Getenv("SLACK_TOKEN"); token != "" {
return token
}

keyringToken, err := keyring.Get(keyringService, keyringUser)
if err == nil && keyringToken != "" {
return keyringToken
}

return ""
}

Expand All @@ -110,7 +110,7 @@ func sendMessage(ctx context.Context, api *slack.Client, identifier, body, times

// Build message options
options := []slack.MsgOption{slack.MsgOptionText(mrkdwnBody, false)}

// If timestamp is provided, add it to create a threaded reply
if timestamp != "" {
options = append(options, slack.MsgOptionTS(timestamp))
Expand All @@ -127,23 +127,23 @@ func sendMessage(ctx context.Context, api *slack.Client, identifier, body, times
fmt.Printf("Message sent to %s (%s)\n", identifier, channel)
fmt.Printf("thread-ts: %s\n", respTimestamp)
}

return respTimestamp, nil
}

func configureToken(ctx context.Context) error {
fmt.Fprintln(os.Stderr, "To get your Slack API token, visit: https://api.slack.com/apps")
fmt.Fprintln(os.Stderr, "Create an app, install it to your workspace, and copy the Bot User OAuth Token")
fmt.Fprint(os.Stderr, "Enter your Slack API token: ")

var token string

// Check if stdin is a terminal
if term.IsTerminal(int(os.Stdin.Fd())) {
// Read password without echoing to terminal
tokenBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintln(os.Stderr) // Print newline after password input

if err != nil {
return fmt.Errorf("failed to read token: %w", err)
}
Expand Down
16 changes: 8 additions & 8 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ func TestConfigure_WhitespaceToken(t *testing.T) {
func TestRun_MissingSubCommand(t *testing.T) {
ctx := context.Background()
err := run(ctx, []string{})

if err == nil {
t.Error("Expected error for missing sub-command, got nil")
}

if !strings.Contains(err.Error(), "missing sub-command") {
t.Errorf("Expected 'missing sub-command' error, got: %v", err)
}
Expand All @@ -52,11 +52,11 @@ func TestRun_UnknownSubCommand(t *testing.T) {

ctx := context.Background()
err := run(ctx, []string{"unknown-command"})

if err == nil {
t.Error("Expected error for unknown sub-command, got nil")
}

if !strings.Contains(err.Error(), "unknown sub-command") {
t.Errorf("Expected 'unknown sub-command' error, got: %v", err)
}
Expand All @@ -83,7 +83,7 @@ func TestRun_SendMessageMissingArgs(t *testing.T) {
}()

ctx := context.Background()

// Test with no arguments
err := run(ctx, []string{"send-message"})
if err == nil {
Expand All @@ -92,7 +92,7 @@ func TestRun_SendMessageMissingArgs(t *testing.T) {
if !strings.Contains(err.Error(), "usage:") {
t.Errorf("Expected usage error, got: %v", err)
}

// Test with only channel
err = run(ctx, []string{"send-message", "C1234567890"})
if err == nil {
Expand All @@ -115,11 +115,11 @@ func TestRun_SendMessageMissingToken(t *testing.T) {

ctx := context.Background()
err := run(ctx, []string{"send-message", "C1234567890", "test message"})

if err == nil {
t.Error("Expected error for missing token, got nil")
}

if !strings.Contains(err.Error(), "Slack token must be set") {
t.Errorf("Expected 'Slack token must be set' error, got: %v", err)
}
Expand Down
6 changes: 3 additions & 3 deletions markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ func convertMarkdownToMrkdwn(markdown string) string {

// Placeholder to protect already-converted bold text
const boldPlaceholder = "\x00BOLD\x00"

// Store bold conversions with placeholders
boldMatches := []string{}

// Convert bold: **text** or __text__ -> placeholder
boldPattern := regexp.MustCompile(`\*\*(.+?)\*\*`)
text = boldPattern.ReplaceAllStringFunc(text, func(match string) string {
Expand All @@ -27,7 +27,7 @@ func convertMarkdownToMrkdwn(markdown string) string {
}
return match
})

boldUnderscorePattern := regexp.MustCompile(`__(.+?)__`)
text = boldUnderscorePattern.ReplaceAllStringFunc(text, func(match string) string {
content := boldUnderscorePattern.FindStringSubmatch(match)
Expand Down
6 changes: 3 additions & 3 deletions mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestRun_MCPServer(t *testing.T) {
// Test that mcp-server sub-command is recognized (won't actually run the server in this test)
// We would need to mock stdin/stdout to fully test this
args := []string{"mcp-server"}

// We can't easily test the full server without mocking stdin/stdout
// but we can verify the command is recognized and doesn't return "unknown sub-command"
_ = args
Expand All @@ -41,11 +41,11 @@ func TestRun_MCPServerMissingToken(t *testing.T) {

ctx := context.Background()
err := run(ctx, []string{"mcp-server"})

if err == nil {
t.Error("Expected error for missing token, got nil")
}

if !strings.Contains(err.Error(), "Slack token must be set") {
t.Errorf("Expected 'Slack token must be set' error, got: %v", err)
}
Expand Down