ask-slack 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/ask/skills/slack.compose/SKILL.md +168 -0
- data/lib/ask/slack/client.rb +51 -17
- data/lib/ask/slack/version.rb +1 -1
- metadata +58 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 01d86ade7e8ae10d0ea5370bf29ad2d5817dab29d6fed2d210bdf6a900498206
|
|
4
|
+
data.tar.gz: ba00d9649330d05b973d12a7d173c28337b8c5d0a54236b6c0c40d45902181d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53cc6ece6ba677124a33a25cff481df7e3dafcd1bff49186211e46ba30f6f64089263d9585eda6a1041191e7288659234d3f2d41c1730d04376a216a023f7822
|
|
7
|
+
data.tar.gz: 15db0b3dfcb89f293ceda3dbb18c9623ceaa8f5344c6664a88ca32e4df6fe7952eb23862863a761e004faeade2223b557f618134cbe5a90cea88a7ab83e8fea6
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slack.compose
|
|
3
|
+
description: How to format and send effective Slack messages — blocks, attachments, markdown, and threading
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use this skill when you need to communicate via Slack — sending messages,
|
|
7
|
+
formatting rich content, or managing conversations with the Slack API.
|
|
8
|
+
|
|
9
|
+
## Step 1: Establish the Channel and Context
|
|
10
|
+
|
|
11
|
+
Before sending any message, confirm:
|
|
12
|
+
|
|
13
|
+
- **Channel** — public channel name (e.g. `#general`) or user DM ID
|
|
14
|
+
- **Intent** — are you informing, asking, alerting, or collaborating?
|
|
15
|
+
- **Thread** — should this be a new message or reply in an existing thread?
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
client = Ask::Slack.client
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Step 2: Choose the Message Format
|
|
22
|
+
|
|
23
|
+
Slack supports three levels of message formatting:
|
|
24
|
+
|
|
25
|
+
**Plain text** — Simple, fast, use for ephemeral or bot messages:
|
|
26
|
+
```ruby
|
|
27
|
+
client.chat_postMessage(channel: "#general", text: "Deploy finished successfully!")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Markdown-ish text** — Use basic formatting in `text`:
|
|
31
|
+
- `*bold*` for emphasis
|
|
32
|
+
- `_italic_` for book titles or terms
|
|
33
|
+
- `~strikethrough~` for obsolete info
|
|
34
|
+
- `` `code` `` for inline code
|
|
35
|
+
- ``````code block`````` for multi-line code
|
|
36
|
+
- `>quote` for blockquotes
|
|
37
|
+
|
|
38
|
+
**Blocks** — Rich interactive messages with structured layout:
|
|
39
|
+
```ruby
|
|
40
|
+
client.chat_postMessage(channel: "#deploy", blocks: [
|
|
41
|
+
{
|
|
42
|
+
type: "header",
|
|
43
|
+
text: { type: "plain_text", text: "🚀 Deploy Complete" }
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: "section",
|
|
47
|
+
text: { type: "mrkdwn", text: "Version *v2.3.1* deployed to *production*" }
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: "context",
|
|
51
|
+
elements: [
|
|
52
|
+
{ type: "mrkdwn", text: "By: @kaka | Duration: 2m 34s" }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Step 3: Use Block Kit for Rich Messages
|
|
59
|
+
|
|
60
|
+
Block Kit is the recommended way to compose Slack messages. Key block types:
|
|
61
|
+
|
|
62
|
+
**Header** — Bold, centered title (1 per message recommended):
|
|
63
|
+
```ruby
|
|
64
|
+
{ type: "header", text: { type: "plain_text", text: "📋 Report Title" } }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Section** — Body content with optional accessory:
|
|
68
|
+
```ruby
|
|
69
|
+
{ type: "section",
|
|
70
|
+
text: { type: "mrkdwn", text: "Description here" },
|
|
71
|
+
accessory: { type: "button", text: { type: "plain_text", text: "View" }, value: "view", url: "..." } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Divider** — Visual separator:
|
|
75
|
+
```ruby
|
|
76
|
+
{ type: "divider" }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Context** — Small text for metadata, timestamps, authorship:
|
|
80
|
+
```ruby
|
|
81
|
+
{ type: "context", elements: [{ type: "mrkdwn", text: "Posted 2h ago" }] }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Fields** — Multi-column layout in a section:
|
|
85
|
+
```ruby
|
|
86
|
+
{ type: "section",
|
|
87
|
+
fields: [
|
|
88
|
+
{ type: "mrkdwn", text: "*Status:*\n✅ Complete" },
|
|
89
|
+
{ type: "mrkdwn", text: "*Duration:*\n45s" }
|
|
90
|
+
] }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Actions** — Interactive buttons (avoid more than 3):
|
|
94
|
+
```ruby
|
|
95
|
+
{ type: "actions", elements: [
|
|
96
|
+
{ type: "button", text: { type: "plain_text", text: "Approve" }, style: "primary", value: "approve" },
|
|
97
|
+
{ type: "button", text: { type: "plain_text", text: "Reject" }, style: "danger", value: "reject" }
|
|
98
|
+
] }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Step 4: Handle Threading
|
|
102
|
+
|
|
103
|
+
Reply in a thread to keep conversations organized:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
# Reply to a specific message
|
|
107
|
+
client.chat_postMessage(
|
|
108
|
+
channel: "#general",
|
|
109
|
+
thread_ts: "1234567890.123456", # parent message timestamp
|
|
110
|
+
text: "I've looked into this — here's what I found..."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Broadcast reply to channel as well (use sparingly)
|
|
114
|
+
client.chat_postMessage(
|
|
115
|
+
channel: "#general",
|
|
116
|
+
thread_ts: "1234567890.123456",
|
|
117
|
+
text: "Update for everyone",
|
|
118
|
+
reply_broadcast: true
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Step 5: Handle Errors
|
|
123
|
+
|
|
124
|
+
Slack API errors are converted to `Ask::Auth::InvalidCredential` for auth failures.
|
|
125
|
+
For other errors, check the error message:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
begin
|
|
129
|
+
client.chat_postMessage(channel: "#unknown", text: "test")
|
|
130
|
+
rescue Slack::Web::Api::Errors::ChannelNotFound => e
|
|
131
|
+
# Channel doesn't exist or bot not invited
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Common errors:
|
|
136
|
+
- `channel_not_found` — Bot not in channel or channel doesn't exist
|
|
137
|
+
- `not_in_channel` — Bot needs to be invited: `/invite @botname`
|
|
138
|
+
- `invalid_blocks` — Block Kit JSON validation error
|
|
139
|
+
- `rate_limited` — Too many requests, retry with backoff
|
|
140
|
+
- `msg_too_long` — Message exceeds 40,000 character limit
|
|
141
|
+
|
|
142
|
+
## Step 6: Upload Files
|
|
143
|
+
|
|
144
|
+
For longer content, upload as a file:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
client.files_upload_v2(
|
|
148
|
+
channels: "#general",
|
|
149
|
+
content: "Long content here...",
|
|
150
|
+
filename: "report.txt",
|
|
151
|
+
title: "Deploy Report"
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Formatting Reference
|
|
156
|
+
|
|
157
|
+
| Format | Markdown | Result |
|
|
158
|
+
|--------|----------|--------|
|
|
159
|
+
| Bold | `*text*` | **text** |
|
|
160
|
+
| Italic | `_text_` | *text* |
|
|
161
|
+
| Strikethrough | `~text~` | ~~text~~ |
|
|
162
|
+
| Code | `` `code` `` | `code` |
|
|
163
|
+
| Code block | ```` ```code``` ```` | ⬛ code |
|
|
164
|
+
| Quote | `>text` | blockquote |
|
|
165
|
+
| Link | `<https://...\|label>` | [label](...) |
|
|
166
|
+
| User mention | `<@U12345>` | @username |
|
|
167
|
+
| Channel | `<#C12345>` | #channel |
|
|
168
|
+
| Emoji | `:rocket:` | 🚀 |
|
data/lib/ask/slack/client.rb
CHANGED
|
@@ -8,12 +8,19 @@ module Ask
|
|
|
8
8
|
# Returns an authenticated Slack Web API client configured for an AI agent.
|
|
9
9
|
#
|
|
10
10
|
# Resolves the Slack token via +Ask::Auth.resolve(:slack_token)+ and
|
|
11
|
-
#
|
|
12
|
-
# authentication errors into +Ask::Auth::InvalidCredential+.
|
|
11
|
+
# configures the client with sensible defaults:
|
|
13
12
|
#
|
|
14
|
-
#
|
|
15
|
-
# -
|
|
16
|
-
#
|
|
13
|
+
# - +timeout+: 30 seconds for HTTP requests
|
|
14
|
+
# - +open_timeout+: 10 seconds for TCP connection
|
|
15
|
+
#
|
|
16
|
+
# Retry behaviour: +ClientProxy+ retries transient Faraday and network
|
|
17
|
+
# errors up to 3 times with exponential backoff before raising
|
|
18
|
+
# +Ask::Auth::InvalidCredential+.
|
|
19
|
+
#
|
|
20
|
+
# The client is wrapped in a +ClientProxy+ that:
|
|
21
|
+
# 1. Converts auth errors (+NotAuthed+, +InvalidAuth+, etc.) into
|
|
22
|
+
# +Ask::Auth::InvalidCredential+
|
|
23
|
+
# 2. Retries transient network failures with exponential backoff
|
|
17
24
|
#
|
|
18
25
|
# @example
|
|
19
26
|
# client = Ask::Slack.client
|
|
@@ -23,27 +30,54 @@ module Ask
|
|
|
23
30
|
# @return [::Slack::Web::Client] an authenticated client
|
|
24
31
|
# @raise [Ask::Auth::MissingCredential] if no Slack token is configured
|
|
25
32
|
# @raise [Ask::Auth::InvalidCredential] if the token is rejected
|
|
26
|
-
def self.client
|
|
33
|
+
def self.client(base_delay: nil)
|
|
27
34
|
token = Ask::Auth.resolve(:slack_token)
|
|
28
35
|
|
|
29
|
-
ClientProxy.new(::Slack::Web::Client.new(
|
|
36
|
+
ClientProxy.new(::Slack::Web::Client.new(
|
|
37
|
+
token: token,
|
|
38
|
+
timeout: 30,
|
|
39
|
+
open_timeout: 10
|
|
40
|
+
), base_delay: base_delay)
|
|
30
41
|
end
|
|
31
42
|
|
|
32
|
-
# Proxies method calls to a +::Slack::Web::Client+, converting
|
|
33
|
-
#
|
|
43
|
+
# Proxies method calls to a +::Slack::Web::Client+, converting auth errors
|
|
44
|
+
# and retrying transient network failures with exponential backoff.
|
|
34
45
|
class ClientProxy < ::BasicObject
|
|
35
|
-
|
|
46
|
+
MAX_RETRIES = 3
|
|
47
|
+
RETRYABLE_ERRORS = [
|
|
48
|
+
::Faraday::TimeoutError,
|
|
49
|
+
::Faraday::ConnectionFailed,
|
|
50
|
+
::Faraday::ServerError,
|
|
51
|
+
::Errno::ECONNREFUSED,
|
|
52
|
+
::Errno::ECONNRESET,
|
|
53
|
+
::Timeout::Error
|
|
54
|
+
].freeze
|
|
55
|
+
|
|
56
|
+
attr_reader :client
|
|
57
|
+
|
|
58
|
+
def initialize(client, base_delay: nil)
|
|
36
59
|
@client = client
|
|
60
|
+
@_base_delay = base_delay || 1
|
|
37
61
|
end
|
|
38
62
|
|
|
39
63
|
def method_missing(name, ...)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
retry_count = 0
|
|
65
|
+
begin
|
|
66
|
+
@client.public_send(name, ...)
|
|
67
|
+
rescue ::Slack::Web::Api::Errors::NotAuthed,
|
|
68
|
+
::Slack::Web::Api::Errors::InvalidAuth,
|
|
69
|
+
::Slack::Web::Api::Errors::AccountInactive,
|
|
70
|
+
::Slack::Web::Api::Errors::TokenRevoked,
|
|
71
|
+
::Slack::Web::Api::Errors::TokenExpired
|
|
72
|
+
::Kernel.raise ::Ask::Auth::InvalidCredential, :slack_token
|
|
73
|
+
rescue *RETRYABLE_ERRORS => e
|
|
74
|
+
retry_count += 1
|
|
75
|
+
if retry_count <= MAX_RETRIES
|
|
76
|
+
::Kernel.sleep(@_base_delay * (2 ** (retry_count - 1)))
|
|
77
|
+
retry
|
|
78
|
+
end
|
|
79
|
+
::Kernel.raise ::Ask::Auth::InvalidCredential.new(:slack_token, e.message)
|
|
80
|
+
end
|
|
47
81
|
end
|
|
48
82
|
|
|
49
83
|
def respond_to_missing?(name, include_private = false)
|
data/lib/ask/slack/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-slack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '3.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: faraday-retry
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.4'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.4'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: minitest
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -79,6 +93,48 @@ dependencies:
|
|
|
79
93
|
- - "~>"
|
|
80
94
|
- !ruby/object:Gem::Version
|
|
81
95
|
version: '13.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: vcr
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '6.3'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '6.3'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: webmock
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '3.23'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '3.23'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: simplecov
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0.22'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0.22'
|
|
82
138
|
description: Provides authenticated Slack client helper, context metadata, and error
|
|
83
139
|
guide for AI agents.
|
|
84
140
|
email:
|
|
@@ -90,6 +146,7 @@ files:
|
|
|
90
146
|
- LICENSE
|
|
91
147
|
- README.md
|
|
92
148
|
- lib/ask-slack.rb
|
|
149
|
+
- lib/ask/skills/slack.compose/SKILL.md
|
|
93
150
|
- lib/ask/slack/client.rb
|
|
94
151
|
- lib/ask/slack/context.rb
|
|
95
152
|
- lib/ask/slack/error_guide.rb
|