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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b550c9169e3b980cb31099c840db9da17a79d2b1164ef92ba3b6d85080a68f3f
4
- data.tar.gz: 484183cfcf1a3b76e11f3c3b1d09382c2d4acc72e2d73c538c8b0448fd8c2d55
3
+ metadata.gz: 01d86ade7e8ae10d0ea5370bf29ad2d5817dab29d6fed2d210bdf6a900498206
4
+ data.tar.gz: ba00d9649330d05b973d12a7d173c28337b8c5d0a54236b6c0c40d45902181d5
5
5
  SHA512:
6
- metadata.gz: ddf878f447dfd83a0130daf887c4b629fbaa5af92513d4908fdaf9789b787505e2bde3f9cd334ce8b3890d427d2fea350b53d2eb7d9955f9edf8c878519f9283
7
- data.tar.gz: 80b75a4d986f37c174b46435641f623b3ff46267b0cf9263cc95478c5081da37b19bcfac10108b83aef35d12365ddc19ac03ca7b6d8f01afc5f0638b5cda2078
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:` | 🚀 |
@@ -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
- # wraps the client in a proxy that converts +::Slack::Web::Api::Errors::SlackError+
12
- # authentication errors into +Ask::Auth::InvalidCredential+.
11
+ # configures the client with sensible defaults:
13
12
  #
14
- # Configuration:
15
- # - Uses the resolved token for authentication
16
- # - Default user agent is set by slack-ruby-client
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(token: token))
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 authentication
33
- # errors into +Ask::Auth::InvalidCredential+.
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
- def initialize(client)
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
- @client.public_send(name, ...)
41
- rescue ::Slack::Web::Api::Errors::NotAuthed,
42
- ::Slack::Web::Api::Errors::InvalidAuth,
43
- ::Slack::Web::Api::Errors::AccountInactive,
44
- ::Slack::Web::Api::Errors::TokenRevoked,
45
- ::Slack::Web::Api::Errors::TokenExpired
46
- ::Kernel.raise ::Ask::Auth::InvalidCredential, :slack_token
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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Slack
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.1'
6
6
  end
7
7
  end
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.0
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