slack_profile 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cb76f24d5ce044a37f9d998cec1379a893a56284485e49880f46f20a49f08894
4
+ data.tar.gz: fbb1ae6983d9e4c0711a4b4f83353d9bab6715e327926efa8fdd289813e266eb
5
+ SHA512:
6
+ metadata.gz: 65371e0b40dfdb651771d3b90b8f38b7769f12af007a54536f57345fbd945c1ff3684e0e261dbcc1ba508ed6cdf51c436051cfdb6cc7db7e3ae826d25770bcad
7
+ data.tar.gz: 65c0849528d6a15378fac996f6aca89fcaa333c60936406c1fff2bae75cfcbd2bb8d7ebe141db56341f54a97e3d7b2350f29a578866f1944894f91b45573e244
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Slack Profile CLI (Ruby)
2
+
3
+ Ruby gem implementation of the Slack Profile CLI tool.
4
+
5
+ ## Installation
6
+
7
+ ### Via RubyGems
8
+
9
+ ```bash
10
+ gem install slack_profile
11
+ ```
12
+
13
+ ### From Source
14
+
15
+ ```bash
16
+ cd ruby
17
+ bundle install
18
+ rake install
19
+ ```
20
+
21
+ ## Setup
22
+
23
+ Configure your Slack token (choose one method):
24
+
25
+ **Option 1: Environment variable (recommended)**
26
+ ```bash
27
+ export SLACK_TOKEN=xoxp-your-token-here
28
+ ```
29
+
30
+ **Option 2: .env file**
31
+ ```bash
32
+ cp .env.example .env
33
+ # Edit .env and add your token
34
+ ```
35
+
36
+ **Option 3: Command line flag**
37
+ ```bash
38
+ slack-profile --token xoxp-your-token-here <command>
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ### Interactive Mode
44
+
45
+ ```bash
46
+ slack-profile
47
+ # or
48
+ slack-profile interactive
49
+ ```
50
+
51
+ ### CLI Commands
52
+
53
+ ```bash
54
+ # Show version
55
+ slack-profile version
56
+
57
+ # List available fields
58
+ slack-profile list-fields
59
+
60
+ # Set a single field
61
+ slack-profile set-field -u U1234567890 -n title -v "Engineer"
62
+
63
+ # Set multiple fields
64
+ slack-profile set-profile -u U1234567890 -p '{"title":"CTO","phone":"555-1234"}'
65
+
66
+ # Batch update
67
+ slack-profile batch-field -u U123,U456,U789 -n title -v "Engineer"
68
+ ```
69
+
70
+ ## Development
71
+
72
+ ```bash
73
+ # Install dependencies
74
+ bundle install
75
+
76
+ # Run tests (when available)
77
+ rake spec
78
+
79
+ # Build the gem
80
+ gem build slack_profile.gemspec
81
+
82
+ # Install locally
83
+ gem install ./slack_profile-1.0.0.gem
84
+ ```
85
+
86
+ ## Publishing to RubyGems
87
+
88
+ ```bash
89
+ # Build the gem
90
+ gem build slack_profile.gemspec
91
+
92
+ # Push to RubyGems
93
+ gem push slack_profile-1.0.0.gem
94
+
95
+ # Create GitHub release for Homebrew
96
+ git tag v1.0.0
97
+ git push origin v1.0.0
98
+ ```
99
+
100
+ See the main [README](../README.md) for full documentation.
data/bin/slack-profile ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/slack_profile"
4
+
5
+ SlackProfile::CLI.start(ARGV)
@@ -0,0 +1,318 @@
1
+ require "thor"
2
+ require "tty-prompt"
3
+
4
+ module SlackProfile
5
+ class CLI < Thor
6
+ class_option :token, type: :string, aliases: "-t", desc: "Slack API token (overrides SLACK_TOKEN env var)"
7
+
8
+ def self.exit_on_failure?
9
+ true
10
+ end
11
+
12
+ desc "interactive", "Interactive mode - prompts for all inputs"
13
+ def interactive
14
+ client = get_client
15
+ prompt = TTY::Prompt.new
16
+
17
+ # Fetch custom fields on startup
18
+ custom_fields = {}
19
+ begin
20
+ puts "🔄 Fetching available fields...\n\n"
21
+ custom_fields = client.get_team_profile
22
+ rescue => e
23
+ puts "⚠️ Could not fetch custom fields: #{e.message}\n\n"
24
+ end
25
+
26
+ loop do
27
+ puts "🚀 Slack Profile CLI - Interactive Mode\n\n"
28
+
29
+ action = prompt.select("What would you like to do?", filter: true, per_page: 10) do |menu|
30
+ menu.choice "Get user profile", :get
31
+ menu.choice "Set a single field for one user", :single
32
+ menu.choice "Set a single field for multiple users", :batch_single
33
+ menu.choice "Set multiple fields for one user", :multiple
34
+ menu.choice "Set multiple fields for multiple users", :batch_multiple
35
+ menu.choice "List available fields", :list
36
+ menu.choice "Exit", :exit
37
+ end
38
+
39
+ break if action == :exit
40
+
41
+ case action
42
+ when :get
43
+ user_id = prompt.ask("Enter the Slack user ID:") do |q|
44
+ q.validate(/^U\w+/, "User ID should start with 'U'")
45
+ q.required true
46
+ end
47
+
48
+ profile = client.get_profile(user_id)
49
+ puts "\n📋 Profile for user #{user_id}:"
50
+ puts JSON.pretty_generate(profile)
51
+ puts ""
52
+
53
+ when :list
54
+ puts "\n📋 Available profile fields:"
55
+ puts "\nStandard fields:"
56
+ standard_fields.each { |field| puts " • #{field}" }
57
+
58
+ if custom_fields.any?
59
+ puts "\nCustom fields:"
60
+ custom_fields.each do |id, field|
61
+ puts " • #{id} - #{field['label']} (#{field['type']})"
62
+ end
63
+ else
64
+ puts "\n📝 No custom fields configured."
65
+ end
66
+ puts ""
67
+
68
+ when :single, :batch_single
69
+ user_ids = if action == :batch_single
70
+ prompt.ask("Enter Slack user IDs (comma-separated):") do |q|
71
+ q.required true
72
+ end.split(",").map(&:strip)
73
+ else
74
+ [prompt.ask("Enter the Slack user ID:") do |q|
75
+ q.validate(/^U\w+/, "User ID should start with 'U'")
76
+ q.required true
77
+ end]
78
+ end
79
+
80
+ # Build field choices
81
+ field_choices = standard_fields.map { |f| { name: f, value: f } }
82
+ custom_fields.each do |id, field|
83
+ field_choices << { name: "#{field['label']} (#{id})", value: id }
84
+ end
85
+ field_choices << { name: "Enter field ID manually", value: :custom }
86
+
87
+ field_name = prompt.select("Which field would you like to update?", field_choices, filter: true, per_page: 15)
88
+
89
+ if field_name == :custom
90
+ field_name = prompt.ask("Enter the field name or ID:")
91
+ end
92
+
93
+ value = prompt.ask("Enter the value for #{field_name} (leave empty to clear):", default: "")
94
+
95
+ if action == :single
96
+ client.set_single_field(user_ids[0], field_name, value)
97
+ puts "✅ Successfully updated #{field_name} for user #{user_ids[0]}\n\n"
98
+ else
99
+ puts "🚀 Updating #{field_name} for #{user_ids.length} users..."
100
+ success_count = 0
101
+ user_ids.each do |user_id|
102
+ begin
103
+ client.set_single_field(user_id, field_name, value)
104
+ puts "✅ Updated #{field_name} for user #{user_id}"
105
+ success_count += 1
106
+ rescue => e
107
+ puts "❌ Failed to update #{field_name} for user #{user_id}: #{e.message}"
108
+ end
109
+ end
110
+ puts "\n📊 Summary: #{success_count}/#{user_ids.length} users updated successfully\n\n"
111
+ end
112
+
113
+ when :multiple, :batch_multiple
114
+ user_ids = if action == :batch_multiple
115
+ prompt.ask("Enter Slack user IDs (comma-separated):") do |q|
116
+ q.required true
117
+ end.split(",").map(&:strip)
118
+ else
119
+ [prompt.ask("Enter the Slack user ID:") do |q|
120
+ q.validate(/^U\w+/, "User ID should start with 'U'")
121
+ q.required true
122
+ end]
123
+ end
124
+
125
+ puts "\n📝 You can set multiple fields using JSON format."
126
+ puts 'Example: {"first_name":"John","last_name":"Doe","title":"Developer"}'
127
+
128
+ profile_json = prompt.ask("Enter profile data as JSON:") do |q|
129
+ q.required true
130
+ q.validate ->(input) { JSON.parse(input); true rescue false }, "Invalid JSON format"
131
+ end
132
+
133
+ profile = JSON.parse(profile_json)
134
+
135
+ if action == :multiple
136
+ client.set_profile(user_ids[0], profile)
137
+ puts "✅ Successfully updated profile for user #{user_ids[0]}\n\n"
138
+ else
139
+ puts "🚀 Updating profiles for #{user_ids.length} users..."
140
+ success_count = 0
141
+ user_ids.each do |user_id|
142
+ begin
143
+ client.set_profile(user_id, profile)
144
+ puts "✅ Updated profile for user #{user_id}"
145
+ success_count += 1
146
+ rescue => e
147
+ puts "❌ Failed to update profile for user #{user_id}: #{e.message}"
148
+ end
149
+ end
150
+ puts "\n📊 Summary: #{success_count}/#{user_ids.length} users updated successfully\n\n"
151
+ end
152
+ end
153
+ end
154
+
155
+ puts "👋 Goodbye!"
156
+ rescue TTY::Reader::InputInterrupt
157
+ puts "\n\n👋 Goodbye!"
158
+ exit 0
159
+ end
160
+
161
+ desc "set-field", "Set a single profile field for a user"
162
+ option :user, required: true, aliases: "-u", desc: "Slack user ID"
163
+ option :name, required: true, aliases: "-n", desc: "Field name"
164
+ option :value, required: true, aliases: "-v", desc: "Field value"
165
+ def set_field
166
+ client = get_client
167
+ client.set_single_field(options[:user], options[:name], options[:value])
168
+ puts "✅ Successfully updated #{options[:name]} for user #{options[:user]}"
169
+ rescue Error => e
170
+ puts "❌ Error: #{e.message}"
171
+ exit 1
172
+ end
173
+
174
+ desc "set-profile", "Set multiple profile fields for a user using JSON"
175
+ option :user, required: true, aliases: "-u", desc: "Slack user ID"
176
+ option :profile, required: true, aliases: "-p", desc: "Profile data as JSON string"
177
+ def set_profile
178
+ client = get_client
179
+ profile = JSON.parse(options[:profile])
180
+ client.set_profile(options[:user], profile)
181
+ puts "✅ Successfully updated profile for user #{options[:user]}"
182
+ rescue JSON::ParserError
183
+ puts "❌ Error: Invalid JSON in profile data"
184
+ exit 1
185
+ rescue Error => e
186
+ puts "❌ Error: #{e.message}"
187
+ exit 1
188
+ end
189
+
190
+ desc "batch-field", "Set a single field for multiple users"
191
+ option :users, required: true, aliases: "-u", desc: "Comma-separated list of Slack user IDs"
192
+ option :name, required: true, aliases: "-n", desc: "Field name"
193
+ option :value, required: true, aliases: "-v", desc: "Field value"
194
+ def batch_field
195
+ client = get_client
196
+ user_ids = options[:users].split(",").map(&:strip).reject(&:empty?)
197
+
198
+ if user_ids.empty?
199
+ puts "❌ Error: No valid user IDs provided"
200
+ exit 1
201
+ end
202
+
203
+ puts "🚀 Updating #{options[:name]} for #{user_ids.length} users..."
204
+ success_count = 0
205
+ errors = 0
206
+
207
+ user_ids.each do |user_id|
208
+ begin
209
+ client.set_single_field(user_id, options[:name], options[:value])
210
+ puts "✅ Updated #{options[:name]} for user #{user_id}"
211
+ success_count += 1
212
+ rescue => e
213
+ puts "❌ Failed to update #{options[:name]} for user #{user_id}: #{e.message}"
214
+ errors += 1
215
+ end
216
+ end
217
+
218
+ puts "\n📊 Summary: #{success_count}/#{user_ids.length} users updated successfully"
219
+ exit 1 if errors > 0
220
+ end
221
+
222
+ desc "batch-profile", "Set multiple fields for multiple users using JSON"
223
+ option :users, required: true, aliases: "-u", desc: "Comma-separated list of Slack user IDs"
224
+ option :profile, required: true, aliases: "-p", desc: "Profile data as JSON string"
225
+ def batch_profile
226
+ client = get_client
227
+ user_ids = options[:users].split(",").map(&:strip).reject(&:empty?)
228
+
229
+ if user_ids.empty?
230
+ puts "❌ Error: No valid user IDs provided"
231
+ exit 1
232
+ end
233
+
234
+ profile = JSON.parse(options[:profile])
235
+
236
+ puts "🚀 Updating profiles for #{user_ids.length} users..."
237
+ success_count = 0
238
+ errors = 0
239
+
240
+ user_ids.each do |user_id|
241
+ begin
242
+ client.set_profile(user_id, profile)
243
+ puts "✅ Updated profile for user #{user_id}"
244
+ success_count += 1
245
+ rescue => e
246
+ puts "❌ Failed to update profile for user #{user_id}: #{e.message}"
247
+ errors += 1
248
+ end
249
+ end
250
+
251
+ puts "\n📊 Summary: #{success_count}/#{user_ids.length} users updated successfully"
252
+ exit 1 if errors > 0
253
+ rescue JSON::ParserError
254
+ puts "❌ Error: Invalid JSON in profile data"
255
+ exit 1
256
+ end
257
+
258
+ desc "list-fields", "List available custom profile fields for the team"
259
+ def list_fields
260
+ client = get_client
261
+ fields = client.get_team_profile
262
+
263
+ puts "Available profile fields:"
264
+ puts "\nStandard fields:"
265
+ standard_fields.each { |field| puts " #{field}" }
266
+
267
+ if fields.any?
268
+ puts "\nCustom fields:"
269
+ fields.each do |id, field|
270
+ puts " #{id} - #{field['label']} (#{field['type']})"
271
+ end
272
+ else
273
+ puts "\nNo custom fields configured."
274
+ end
275
+ rescue Error => e
276
+ puts "❌ Error: #{e.message}"
277
+ exit 1
278
+ end
279
+
280
+ desc "version", "Show version"
281
+ def version
282
+ puts "slack-profile version #{SlackProfile::VERSION}"
283
+ end
284
+
285
+ default_task :interactive
286
+
287
+ private
288
+
289
+ def get_client
290
+ token = options[:token] || ENV["SLACK_TOKEN"]
291
+
292
+ unless token
293
+ puts "❌ Error: Slack token is required"
294
+ puts "Provide it via:"
295
+ puts " 1. Command line: --token xoxp-your-token-here"
296
+ puts " 2. Environment variable: export SLACK_TOKEN=xoxp-your-token-here"
297
+ puts " 3. .env file: SLACK_TOKEN=xoxp-your-token-here"
298
+ exit 1
299
+ end
300
+
301
+ Client.new(token)
302
+ end
303
+
304
+ def standard_fields
305
+ %w[
306
+ first_name
307
+ last_name
308
+ display_name
309
+ title
310
+ email
311
+ phone
312
+ pronouns
313
+ real_name
314
+ start_date
315
+ ]
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,84 @@
1
+ require "httparty"
2
+ require "json"
3
+
4
+ module SlackProfile
5
+ class Client
6
+ include HTTParty
7
+ base_uri "https://slack.com/api"
8
+
9
+ def initialize(token)
10
+ @token = token
11
+ @headers = {
12
+ "Authorization" => "Bearer #{@token}",
13
+ "Content-Type" => "application/json"
14
+ }
15
+ end
16
+
17
+ def set_profile(user_id, profile)
18
+ response = self.class.post("/users.profile.set",
19
+ headers: @headers,
20
+ body: {
21
+ user: user_id,
22
+ profile: profile.to_json
23
+ }.to_json
24
+ )
25
+
26
+ handle_response(response)
27
+ end
28
+
29
+ def set_single_field(user_id, field_name, value)
30
+ profile = if field_name.start_with?("X")
31
+ # Custom field
32
+ {
33
+ fields: {
34
+ field_name => {
35
+ value: value,
36
+ alt: ""
37
+ }
38
+ }
39
+ }
40
+ else
41
+ # Standard field
42
+ { field_name => value }
43
+ end
44
+
45
+ set_profile(user_id, profile)
46
+ end
47
+
48
+ def get_profile(user_id)
49
+ response = self.class.get("/users.profile.get",
50
+ headers: @headers,
51
+ query: {
52
+ user: user_id,
53
+ include_labels: true
54
+ }
55
+ )
56
+
57
+ result = handle_response(response)
58
+ result["profile"]
59
+ end
60
+
61
+ def get_team_profile
62
+ response = self.class.get("/team.profile.get",
63
+ headers: @headers
64
+ )
65
+
66
+ result = handle_response(response)
67
+ result.dig("profile", "fields") || {}
68
+ end
69
+
70
+ private
71
+
72
+ def handle_response(response)
73
+ data = JSON.parse(response.body)
74
+
75
+ unless data["ok"]
76
+ raise Error, "Slack API error: #{data['error']}"
77
+ end
78
+
79
+ data
80
+ rescue JSON::ParserError
81
+ raise Error, "Invalid response from Slack API"
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,3 @@
1
+ module SlackProfile
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "dotenv/load"
2
+ require_relative "slack_profile/version"
3
+ require_relative "slack_profile/client"
4
+ require_relative "slack_profile/cli"
5
+
6
+ module SlackProfile
7
+ class Error < StandardError; end
8
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slack_profile
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jasper Mayone
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: thor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: tty-prompt
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.23'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.23'
40
+ - !ruby/object:Gem::Dependency
41
+ name: httparty
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.21'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.21'
54
+ - !ruby/object:Gem::Dependency
55
+ name: dotenv
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.8'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.8'
68
+ description: A command-line tool for updating Slack user profiles using the Slack
69
+ API. Perfect for workspace admins who need to manage user profiles programmatically.
70
+ email:
71
+ - ''
72
+ executables:
73
+ - slack-profile
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - README.md
78
+ - bin/slack-profile
79
+ - lib/slack_profile.rb
80
+ - lib/slack_profile/cli.rb
81
+ - lib/slack_profile/client.rb
82
+ - lib/slack_profile/version.rb
83
+ homepage: https://github.com/jaspermayone/slack-profile-cli
84
+ licenses:
85
+ - MIT
86
+ metadata:
87
+ homepage_uri: https://github.com/jaspermayone/slack-profile-cli
88
+ source_code_uri: https://github.com/jaspermayone/slack-profile-cli
89
+ changelog_uri: https://github.com/jaspermayone/slack-profile-cli/blob/main/CHANGELOG.md
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: 2.7.0
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.7.2
105
+ specification_version: 4
106
+ summary: CLI tool for updating Slack user profiles
107
+ test_files: []