slack_line 1.1 → 1.2

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: 63f2270ed13b9bf4d14585497109923c70b8e7ec8a34af3ebb46306afdfaf703
4
- data.tar.gz: 677793b2b72ef6f1d58673b9c658151b3b22504e85cb34678d6e0973d23f00a6
3
+ metadata.gz: bc23e7f575bc68df1c9824aacbde9b74050c14f82f765fc6929dc33ae909eccb
4
+ data.tar.gz: 45e54647313e4c71a27de6c8bda77826deaf7704ac8a5680c1439c5015f9b50e
5
5
  SHA512:
6
- metadata.gz: 939add34dd7ecd05b44828f53bfd20d62b4c0df9a4deb2b2b2097f736b6a3666173f098e81451416865c0ff66765320b2bff16c8558230eb445a54a3673e9be1
7
- data.tar.gz: 9dc08564bfb998fe4abe612f546a4f9327924ea70e01a153d5daaa751baae3698296b14cb13bc7c50ff612e31f9f1f54dcf6086a74bd5579a17257307027f2d6
6
+ metadata.gz: 5c9d9e4c86c4ae41adac7a71a1044fe02aef53eaa823bc286862178b9fce6de8716d51a7833ed6ee118a550e29726358dd7b2a54df3bc78526074be65a125af4
7
+ data.tar.gz: fc217ed772fbbcbf1a27aa0911d24f2ef716c4b6ffed127f2c0959329b5358e510654a4d313c9d42e8a0168a7b88fe3c5d253d46545775175bd3671c3e593a65
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source "https://gem.coop"
2
2
 
3
3
  gemspec
4
+
5
+ gem "lightly"
data/README.md CHANGED
@@ -80,6 +80,18 @@ be set via ENV or `SlackLine.configure`:
80
80
  * `per_thread_delay` or `SLACK_LINE_PER_THREAD_DELAY` is a float as
81
81
  well - SlackLine will `sleep` for this duration after each _thread_
82
82
  is posted, and after each non-thread message is posted.
83
+ * `cache_path` or `SLACK_LINE_CACHE_PATH` - a directory path for
84
+ disk-caching the users and groups lists fetched from the Slack API.
85
+ When set, SlackLine will use the [lightly](https://github.com/DannyBen/lightly)
86
+ gem to cache results to disk, which avoids redundant API calls across
87
+ separate runs. Requires `lightly` to be installed as an optional
88
+ dependency - if it is not available, a `DiskCaching::NoLightly` error
89
+ will be raised at runtime.
90
+ * `cache_duration` or `SLACK_LINE_CACHE_DURATION` - how long cached
91
+ data remains valid (default `"15m"`). Accepts a plain integer number
92
+ of seconds, or a number followed by a unit suffix: `s` (seconds),
93
+ `m` (minutes), `h` (hours), or `d` (days). For example: `"30m"`,
94
+ `"2h"`, `"1d"`, or `"900"`.
83
95
 
84
96
  You can just set those via the environment variables, but you can also
85
97
  set them on the singleton configuration object:
@@ -92,10 +104,12 @@ SlackLine.configure do |config|
92
104
  config.default_channel = "#ci-flow"
93
105
  config.per_message_delay = 0.2
94
106
  config.per_thread_delay = 2.0
107
+ config.cache_path = "/tmp/slack_line_cache"
108
+ config.cache_duration = "1h"
95
109
  end
96
110
  ```
97
111
 
98
- ## Multiple Configurations
112
+ ### Multiple Configurations
99
113
 
100
114
  If you're working in a context where you need to support multiple
101
115
  SlackLine configurations, don't worry! The singleton central config is
@@ -122,6 +136,126 @@ BAR_SLACK.thread("Message 1", "Message 2").post
122
136
  BAR_SLACK.message("Message 3", to: "#bar-team-3").post
123
137
  ```
124
138
 
139
+ ## CLI Scripts
140
+
141
+ The gem ships with three executable scripts for sending and managing Slack messages
142
+ from the command line. All three accept `-t`/`--slack-token TOKEN` and
143
+ `-n`/`--bot-name NAME` to override the corresponding environment variables.
144
+ Configuration can also come from `SLACK_LINE_SLACK_TOKEN` and friends as described
145
+ above.
146
+
147
+ ### `slack_line_message`
148
+
149
+ Sends, updates, or previews a single Slack message.
150
+
151
+ ```sh
152
+ # Post a simple message
153
+ slack_line_message --post-to "#general" --save /tmp/msg.json "Something happened!"
154
+
155
+ # Preview without posting (prints JSON block kit content)
156
+ slack_line_message "Something happened!"
157
+
158
+ # Post a message using the block-kit DSL on stdin
159
+ echo 'text "Something happened!"' | slack_line_message --post-to "#general" --save /tmp/msg.json
160
+
161
+ # Append a reply to an existing thread (saved from a prior post)
162
+ slack_line_message --append /tmp/msg.json --save /tmp/msg.json "Follow-up!"
163
+
164
+ # Update a previously-sent message in place
165
+ slack_line_message --update /tmp/msg.json "Edited message text"
166
+
167
+ # Update a specific message within a saved thread (0-indexed)
168
+ slack_line_message --update /tmp/msg.json --message-number 2 "Corrected reply"
169
+ ```
170
+
171
+ Options:
172
+
173
+ * `-p`/`--post-to TARGET` - channel or user to post to
174
+ * `-a`/`--append PATH` - append a reply to the thread saved at PATH
175
+ * `-U`/`--update PATH` - update the message (or thread) saved at PATH
176
+ * `-m`/`--message-number N` - which message in a thread to update (0-indexed;
177
+ required with `--update` on a thread)
178
+ * `-s`/`--save PATH` - write the sent/updated result to PATH as JSON
179
+ * `-u`/`--look-up-users` - resolve `@mentions` via the Slack API
180
+ * `--cache-path PATH` / `--cache-duration DURATION` - disk-cache user/group lookups
181
+ * `--no-backoff` - disable per-message sleep delays
182
+
183
+ When no content arguments are given and no DSL is piped, the script reads DSL
184
+ interactively from stdin.
185
+
186
+ ### `slack_line_thread`
187
+
188
+ Sends or previews a thread (multiple messages posted together).
189
+
190
+ ```sh
191
+ # Post a thread from positional string arguments
192
+ slack_line_thread --post-to "#general" --save /tmp/thread.json "First" "Second" "Third"
193
+
194
+ # Preview the thread without posting
195
+ slack_line_thread "First" "Second"
196
+
197
+ # Post a thread from block-kit DSL on stdin
198
+ cat thread.dsl | slack_line_thread --post-to "#general" --save /tmp/thread.json
199
+ ```
200
+
201
+ A DSL block looks like:
202
+
203
+ ```ruby
204
+ message "Simple first message"
205
+ message do
206
+ text "Fancier second message"
207
+ context "with some context"
208
+ end
209
+ ```
210
+
211
+ Options mirror `slack_line_message`, minus the update/append flags:
212
+
213
+ * `-p`/`--post-to TARGET` - channel or user to post to
214
+ * `-s`/`--save PATH` - write the sent result to PATH as JSON
215
+ * `-u`/`--look-up-users`, `--cache-path`, `--cache-duration`, `--no-backoff` -
216
+ same as above
217
+
218
+ ### `slack_line_stateful_thread`
219
+
220
+ Designed for long-running processes that need to post a single status message
221
+ and then keep it updated as state changes - for example, a deployment pipeline
222
+ that posts `[running] Deploy started`, updates it to `[done] Deploy finished`,
223
+ and appends replies along the way. The sent message is persisted to a file;
224
+ subsequent invocations load and re-persist that file to know which Slack message
225
+ to update.
226
+
227
+ Messages are formatted as `[STATE] body`. The `--state` and `--message` flags each
228
+ update their respective part independently; omitting one leaves it unchanged on update.
229
+
230
+ ```sh
231
+ # Initial post - creates /tmp/deploy.json and posts "[running] Deploy started"
232
+ slack_line_stateful_thread --path /tmp/deploy.json --post-to "#deploys" \
233
+ --state running --message "Deploy started"
234
+
235
+ # Update the state only - becomes "[done] Deploy started"
236
+ slack_line_stateful_thread --path /tmp/deploy.json --state done
237
+
238
+ # Update the body only - becomes "[done] Deploy finished"
239
+ slack_line_stateful_thread --path /tmp/deploy.json --message "Deploy finished"
240
+
241
+ # Update both state and body - becomes "[failed] Something went wrong"
242
+ slack_line_stateful_thread --path /tmp/deploy.json --state failed --message "Something went wrong"
243
+
244
+ # Append a thread reply without changing the main message
245
+ slack_line_stateful_thread --path /tmp/deploy.json --thread --message "Step 1 complete"
246
+ ```
247
+
248
+ Options:
249
+
250
+ * `--path PATH` - (required) file path used to persist the sent message between invocations
251
+ * `-p`/`--post-to TARGET` - channel or user; required on the first call, forbidden
252
+ thereafter
253
+ * `-s`/`--state STATE` - the state label shown in brackets; required on first call
254
+ * `-m`/`--message MESSAGE` - the message body; required on first call and when using
255
+ `--thread`
256
+ * `--thread` - append a reply instead of updating the main message (mutually exclusive
257
+ with `--state`)
258
+
125
259
  ## Slack App Permissions
126
260
 
127
261
  In order to post/update messages, the app behind your `SLACK_LINE_TOKEN` can use
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/slack_line"
4
+
5
+ begin
6
+ SlackLine::Cli::SlackLineStatefulThread.new(argv: ARGV).run
7
+ rescue SlackLine::Cli::ExitException => e
8
+ abort e.message
9
+ end
@@ -20,11 +20,13 @@ module SlackLine
20
20
  else
21
21
  run_preview
22
22
  end
23
+ rescue DiskCaching::NoLightly, Configuration::InvalidValue => e
24
+ raise ExitException, e.message
23
25
  end
24
26
 
25
27
  def options
26
28
  return @options if defined?(@options)
27
- opts = {post_to: nil, append: nil, update: nil, message_number: nil, save: nil, slack_token: nil, look_up_users: nil, bot_name: nil, backoff: nil}
29
+ opts = {post_to: nil, append: nil, update: nil, message_number: nil, save: nil, slack_token: nil, look_up_users: nil, bot_name: nil, backoff: nil, cache_path: nil, cache_duration: nil}
28
30
  remaining = option_parser(opts).parse(@argv.dup)
29
31
  if remaining.empty?
30
32
  opts[:dsl] = read_stdin
@@ -37,7 +39,7 @@ module SlackLine
37
39
 
38
40
  def configuration
39
41
  return @configuration if defined?(@configuration)
40
- cfg_opts = options.slice(:slack_token, :look_up_users, :bot_name, :backoff).compact
42
+ cfg_opts = options.slice(:slack_token, :look_up_users, :bot_name, :backoff, :cache_path, :cache_duration).compact
41
43
  @configuration = Configuration.new(nil, **cfg_opts)
42
44
  end
43
45
 
@@ -56,6 +58,8 @@ module SlackLine
56
58
  parser.on("-m", "--message-number N", Integer) { |n| opts[:message_number] = n }
57
59
  parser.on("-s", "--save PATH") { |p| opts[:save] = p }
58
60
  parser.on("--no-backoff") { opts[:backoff] = false }
61
+ parser.on("--cache-path PATH") { |p| opts[:cache_path] = p }
62
+ parser.on("--cache-duration DURATION") { |d| opts[:cache_duration] = d }
59
63
  end
60
64
  end
61
65
 
@@ -0,0 +1,126 @@
1
+ require "optparse"
2
+
3
+ module SlackLine
4
+ module Cli
5
+ class SlackLineStatefulThread
6
+ def initialize(argv:, stdout: $stdout, stderr: $stderr)
7
+ @argv = argv
8
+ @stdout = stdout
9
+ @stderr = stderr
10
+ end
11
+
12
+ def run
13
+ validate_options!
14
+ if path_exists?
15
+ run_subsequent
16
+ else
17
+ run_initial
18
+ end
19
+ rescue DiskCaching::NoLightly, Configuration::InvalidValue => e
20
+ raise ExitException, e.message
21
+ end
22
+
23
+ def options
24
+ return @options if defined?(@options)
25
+ opts = {path: nil, post_to: nil, state: nil, message: nil, thread: nil, slack_token: nil, bot_name: nil}
26
+ option_parser(opts).parse(@argv.dup)
27
+ @options = opts
28
+ end
29
+
30
+ def configuration
31
+ return @configuration if defined?(@configuration)
32
+ cfg_opts = options.slice(:slack_token, :bot_name).compact
33
+ @configuration = Configuration.new(nil, **cfg_opts)
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :stdout, :stderr
39
+
40
+ def option_parser(opts) # rubocop:disable Metrics/AbcSize
41
+ OptionParser.new do |parser|
42
+ parser.on("-t", "--slack-token TOKEN") { |t| opts[:slack_token] = t }
43
+ parser.on("-n", "--bot-name NAME") { |n| opts[:bot_name] = n }
44
+ parser.on("--path PATH") { |p| opts[:path] = p }
45
+ parser.on("-p", "--post-to TARGET") { |t| opts[:post_to] = t }
46
+ parser.on("-s", "--state STATE") { |s| opts[:state] = s }
47
+ parser.on("-m", "--message MESSAGE") { |m| opts[:message] = m }
48
+ parser.on("--thread") { opts[:thread] = true }
49
+ end
50
+ end
51
+
52
+ def validate_options!
53
+ raise ExitException, "--path is required" unless options[:path]
54
+ path_exists? ? validate_subsequent_options! : validate_initial_options!
55
+ end
56
+
57
+ def validate_initial_options!
58
+ raise ExitException, "--thread cannot be used on initial post" if options[:thread]
59
+ raise ExitException, "--post-to is required for initial post" unless options[:post_to]
60
+ raise ExitException, "--state is required for initial post" unless options[:state]
61
+ raise ExitException, "--message is required for initial post" unless options[:message]
62
+ end
63
+
64
+ def validate_subsequent_options!
65
+ raise ExitException, "--post-to cannot be used after initial post" if options[:post_to]
66
+ options[:thread] ? validate_thread_options! : validate_update_options!
67
+ end
68
+
69
+ def validate_thread_options!
70
+ raise ExitException, "--thread cannot be used with --state" if options[:state]
71
+ raise ExitException, "--thread requires --message" unless options[:message]
72
+ end
73
+
74
+ def validate_update_options!
75
+ raise ExitException, "One of --state or --message is required" unless options[:state] || options[:message]
76
+ end
77
+
78
+ def path_exists? = options[:path] && File.exist?(options[:path])
79
+
80
+ def client = @client ||= Client.new(configuration)
81
+
82
+ def load_sent
83
+ @load_sent ||= SlackLine.from_json(JSON.parse(File.read(options[:path])), client:)
84
+ end
85
+
86
+ def save_sent(result) = File.write(options[:path], JSON.pretty_generate(result.as_json))
87
+
88
+ def run_initial
89
+ text = "[#{options[:state]}] #{options[:message]}"
90
+ sent = Message.new(text, client:).post(to: options[:post_to])
91
+ save_sent(sent)
92
+ stderr.puts "Posted stateful thread to #{options[:post_to]}"
93
+ end
94
+
95
+ def run_subsequent
96
+ options[:thread] ? run_thread_message : run_update_message
97
+ end
98
+
99
+ def parse_state_message(text)
100
+ match = text&.match(/\A\[([^\]]*)\] (.+)\z/m)
101
+ raise ExitException, "Cannot parse state and body from stored message content" unless match
102
+ [match[1], match[2]]
103
+ end
104
+
105
+ def current_state_and_body(sent)
106
+ parse_state_message(sent.content.dig(0, "text", "text"))
107
+ end
108
+
109
+ def run_update_message
110
+ sent = load_sent
111
+ current_state, current_body = current_state_and_body(sent)
112
+ new_text = "[#{options[:state] || current_state}] #{options[:message] || current_body}"
113
+ updated = sent.update(Message.new(new_text, client:))
114
+ save_sent(updated)
115
+ stderr.puts "Updated message in #{updated.channel}"
116
+ end
117
+
118
+ def run_thread_message
119
+ sent = load_sent
120
+ sent_thread = sent.append(options[:message])
121
+ save_sent(sent_thread)
122
+ stderr.puts "Threaded message in #{sent_thread.channel}"
123
+ end
124
+ end
125
+ end
126
+ end
@@ -16,11 +16,13 @@ module SlackLine
16
16
  else
17
17
  run_preview
18
18
  end
19
+ rescue DiskCaching::NoLightly, Configuration::InvalidValue => e
20
+ raise ExitException, e.message
19
21
  end
20
22
 
21
23
  def options
22
24
  return @options if defined?(@options)
23
- opts = {post_to: nil, save: nil, slack_token: nil, look_up_users: nil, bot_name: nil, backoff: nil}
25
+ opts = {post_to: nil, save: nil, slack_token: nil, look_up_users: nil, bot_name: nil, backoff: nil, cache_path: nil, cache_duration: nil}
24
26
  remaining = option_parser(opts).parse(@argv.dup)
25
27
  if remaining.empty?
26
28
  opts[:dsl] = read_stdin
@@ -32,7 +34,7 @@ module SlackLine
32
34
 
33
35
  def configuration
34
36
  return @configuration if defined?(@configuration)
35
- cfg_opts = options.slice(:slack_token, :look_up_users, :bot_name, :backoff).compact
37
+ cfg_opts = options.slice(:slack_token, :look_up_users, :bot_name, :backoff, :cache_path, :cache_duration).compact
36
38
  @configuration = Configuration.new(nil, **cfg_opts)
37
39
  end
38
40
 
@@ -48,6 +50,8 @@ module SlackLine
48
50
  parser.on("-p", "--post-to TARGET") { |t| opts[:post_to] = t }
49
51
  parser.on("-s", "--save PATH") { |p| opts[:save] = p }
50
52
  parser.on("--no-backoff") { opts[:backoff] = false }
53
+ parser.on("--cache-path PATH") { |p| opts[:cache_path] = p }
54
+ parser.on("--cache-duration DURATION") { |d| opts[:cache_duration] = d }
51
55
  end
52
56
  end
53
57
 
@@ -16,8 +16,8 @@ module SlackLine
16
16
 
17
17
  def thread(*messages, &dsl_block) = Thread.new(*messages, client: self, &dsl_block)
18
18
 
19
- memoize def users = Users.new(slack_client:)
19
+ memoize def users = Users.new(client: self)
20
20
 
21
- memoize def groups = Groups.new(slack_client:)
21
+ memoize def groups = Groups.new(client: self)
22
22
  end
23
23
  end
@@ -1,12 +1,19 @@
1
1
  module SlackLine
2
2
  class Configuration
3
+ include Memoization
4
+
5
+ InvalidValue = Class.new(Error)
6
+
3
7
  attr_accessor :slack_token,
4
8
  :look_up_users, :bot_name, :default_channel,
5
9
  :per_message_delay, :per_thread_delay,
6
- :backoff
10
+ :backoff, :cache_path
11
+ attr_writer :cache_duration
7
12
 
8
13
  alias_method :look_up_users?, :look_up_users
9
14
 
15
+ memoize def cache_duration = parse_duration(@cache_duration.to_s)
16
+
10
17
  DEFAULTS = {
11
18
  slack_token: nil,
12
19
  look_up_users: false,
@@ -14,7 +21,9 @@ module SlackLine
14
21
  default_channel: nil,
15
22
  per_message_delay: 0.0,
16
23
  per_thread_delay: 0.0,
17
- backoff: true
24
+ backoff: true,
25
+ cache_path: nil,
26
+ cache_duration: "15m"
18
27
  }.freeze
19
28
 
20
29
  def initialize(base_config = nil, **overrides)
@@ -28,6 +37,8 @@ module SlackLine
28
37
  @per_message_delay = cascade(:per_message_delay, "SLACK_LINE_PER_MESSAGE_DELAY", :float)
29
38
  @per_thread_delay = cascade(:per_thread_delay, "SLACK_LINE_PER_THREAD_DELAY", :float)
30
39
  @backoff = cascade(:backoff, "SLACK_LINE_NO_BACKOFF", :inverse_boolean)
40
+ @cache_path = cascade(:cache_path, "SLACK_LINE_CACHE_PATH", :string)
41
+ @cache_duration = cascade(:cache_duration, "SLACK_LINE_CACHE_DURATION", :string)
31
42
  end
32
43
 
33
44
  private
@@ -57,5 +68,15 @@ module SlackLine
57
68
  value
58
69
  end
59
70
  end
71
+
72
+ DURATION_MULTIPLIERS = {s: 1, m: 60, h: 3600, d: 86400}.freeze
73
+
74
+ def parse_duration(value)
75
+ match = value.match(/\A(\d+)([smhd])?\z/)
76
+ raise(InvalidValue, "Invalid duration: #{value.inspect}") unless match
77
+
78
+ digits, unit = match.captures
79
+ digits.to_i * DURATION_MULTIPLIERS.fetch(unit&.to_sym, 1)
80
+ end
60
81
  end
61
82
  end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require "lightly"
3
+ rescue LoadError
4
+ # optional dependency
5
+ end
6
+
7
+ module SlackLine
8
+ module DiskCaching
9
+ NoLightly = Class.new(Error)
10
+
11
+ def cached(config:, key:, &block)
12
+ return yield if config.cache_path.nil?
13
+ raise(NoLightly, "The 'lightly' gem is required for disk caching") unless defined?(Lightly)
14
+
15
+ Lightly.new(dir: config.cache_path, life: config.cache_duration).get(key) { yield }
16
+ end
17
+ end
18
+ end
@@ -1,12 +1,15 @@
1
1
  module SlackLine
2
2
  class Groups
3
3
  include Memoization
4
+ include DiskCaching
4
5
 
5
- def initialize(slack_client:)
6
- @slack_client = slack_client
6
+ def initialize(client:)
7
+ @client = client
7
8
  end
8
9
 
9
- memoize def all = fetch_groups
10
+ memoize def all
11
+ cached(config: client.configuration, key: "groups_all") { fetch_groups }
12
+ end
10
13
 
11
14
  def find(handle:)
12
15
  groups_by_handle[handle.downcase]
@@ -14,7 +17,9 @@ module SlackLine
14
17
 
15
18
  private
16
19
 
17
- attr_reader :slack_client
20
+ attr_reader :client
21
+
22
+ def slack_client = client.slack_client
18
23
 
19
24
  memoize def fetch_groups
20
25
  slack_client.usergroups_list.usergroups || []
@@ -10,9 +10,14 @@ module SlackLine
10
10
  attr_reader :sent_messages
11
11
  alias_method :messages, :sent_messages
12
12
  def_delegators :sent_messages, :each, :map, :size, :first, :last, :empty?
13
- def_delegators :first, :channel, :ts
13
+ def_delegators :first, :channel, :ts, :content
14
14
  alias_method :thread_ts, :ts
15
15
 
16
+ def update(*text_or_blocks, &dsl_block)
17
+ updated_root = first.update(*text_or_blocks, &dsl_block)
18
+ SentThread.new(updated_root, *sent_messages[1..])
19
+ end
20
+
16
21
  def append(*text_or_blocks, &dsl_block)
17
22
  extended = first.append(*text_or_blocks, &dsl_block)
18
23
  SentThread.new(*sent_messages, *extended.sent_messages[1..])
@@ -1,12 +1,15 @@
1
1
  module SlackLine
2
2
  class Users
3
3
  include Memoization
4
+ include DiskCaching
4
5
 
5
- def initialize(slack_client:)
6
- @slack_client = slack_client
6
+ def initialize(client:)
7
+ @client = client
7
8
  end
8
9
 
9
- memoize def all = all_users.reject(&:deleted).reject(&:is_bot)
10
+ memoize def all
11
+ cached(config: client.configuration, key: "users_all") { all_users.reject(&:deleted).reject(&:is_bot) }
12
+ end
10
13
 
11
14
  def find(display_name:)
12
15
  users_by_display_name[display_name.downcase]
@@ -14,7 +17,9 @@ module SlackLine
14
17
 
15
18
  private
16
19
 
17
- attr_reader :slack_client
20
+ attr_reader :client
21
+
22
+ def slack_client = client.slack_client
18
23
 
19
24
  def fetch_page(cursor: nil)
20
25
  params = {limit: 200}
@@ -1,3 +1,3 @@
1
1
  module SlackLine
2
- VERSION = "1.1".freeze
2
+ VERSION = "1.2".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slack_line
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: '1.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Mueller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-02 00:00:00.000000000 Z
11
+ date: 2026-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -173,6 +173,7 @@ email:
173
173
  - nevinera@gmail.com
174
174
  executables:
175
175
  - slack_line_message
176
+ - slack_line_stateful_thread
176
177
  - slack_line_thread
177
178
  extensions: []
178
179
  extra_rdoc_files: []
@@ -190,13 +191,16 @@ files:
190
191
  - Gemfile
191
192
  - README.md
192
193
  - bin/slack_line_message
194
+ - bin/slack_line_stateful_thread
193
195
  - bin/slack_line_thread
194
196
  - lib/slack_line.rb
195
197
  - lib/slack_line/cli.rb
196
198
  - lib/slack_line/cli/slack_line_message.rb
199
+ - lib/slack_line/cli/slack_line_stateful_thread.rb
197
200
  - lib/slack_line/cli/slack_line_thread.rb
198
201
  - lib/slack_line/client.rb
199
202
  - lib/slack_line/configuration.rb
203
+ - lib/slack_line/disk_caching.rb
200
204
  - lib/slack_line/groups.rb
201
205
  - lib/slack_line/memoization.rb
202
206
  - lib/slack_line/message.rb