teems 0.1.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 +7 -0
- data/CHANGELOG.md +24 -0
- data/LICENSE +21 -0
- data/README.md +136 -0
- data/bin/teems +7 -0
- data/lib/teems/api/calendar.rb +94 -0
- data/lib/teems/api/channels.rb +26 -0
- data/lib/teems/api/chats.rb +29 -0
- data/lib/teems/api/client.rb +40 -0
- data/lib/teems/api/files.rb +12 -0
- data/lib/teems/api/messages.rb +58 -0
- data/lib/teems/api/users.rb +88 -0
- data/lib/teems/api/users_mailbox.rb +16 -0
- data/lib/teems/api/users_presence.rb +43 -0
- data/lib/teems/cli.rb +133 -0
- data/lib/teems/commands/activity.rb +222 -0
- data/lib/teems/commands/auth.rb +268 -0
- data/lib/teems/commands/base.rb +146 -0
- data/lib/teems/commands/cal.rb +891 -0
- data/lib/teems/commands/channels.rb +115 -0
- data/lib/teems/commands/chats.rb +159 -0
- data/lib/teems/commands/help.rb +107 -0
- data/lib/teems/commands/messages.rb +281 -0
- data/lib/teems/commands/ooo.rb +385 -0
- data/lib/teems/commands/org.rb +232 -0
- data/lib/teems/commands/status.rb +224 -0
- data/lib/teems/commands/sync.rb +390 -0
- data/lib/teems/commands/who.rb +377 -0
- data/lib/teems/formatters/calendar_formatter.rb +227 -0
- data/lib/teems/formatters/format_utils.rb +56 -0
- data/lib/teems/formatters/markdown_formatter.rb +113 -0
- data/lib/teems/formatters/message_formatter.rb +67 -0
- data/lib/teems/formatters/output.rb +105 -0
- data/lib/teems/models/account.rb +59 -0
- data/lib/teems/models/channel.rb +31 -0
- data/lib/teems/models/chat.rb +111 -0
- data/lib/teems/models/duration.rb +46 -0
- data/lib/teems/models/event.rb +124 -0
- data/lib/teems/models/message.rb +125 -0
- data/lib/teems/models/parsing.rb +56 -0
- data/lib/teems/models/user.rb +25 -0
- data/lib/teems/models/user_profile.rb +45 -0
- data/lib/teems/runner.rb +81 -0
- data/lib/teems/services/api_client.rb +217 -0
- data/lib/teems/services/cache_store.rb +32 -0
- data/lib/teems/services/configuration.rb +56 -0
- data/lib/teems/services/file_downloader.rb +39 -0
- data/lib/teems/services/headless_extract.rb +192 -0
- data/lib/teems/services/safari_oauth.rb +285 -0
- data/lib/teems/services/sync_dir_naming.rb +42 -0
- data/lib/teems/services/sync_engine.rb +194 -0
- data/lib/teems/services/sync_store.rb +193 -0
- data/lib/teems/services/teams_url_parser.rb +78 -0
- data/lib/teems/services/token_exchange_scripts.rb +56 -0
- data/lib/teems/services/token_extractor.rb +401 -0
- data/lib/teems/services/token_extractor_scripts.rb +116 -0
- data/lib/teems/services/token_refresher.rb +169 -0
- data/lib/teems/services/token_store.rb +116 -0
- data/lib/teems/support/error_logger.rb +35 -0
- data/lib/teems/support/help_formatter.rb +80 -0
- data/lib/teems/support/timezone.rb +44 -0
- data/lib/teems/support/xdg_paths.rb +62 -0
- data/lib/teems/version.rb +5 -0
- data/lib/teems.rb +117 -0
- data/support/token_helper.swift +485 -0
- metadata +110 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teems
|
|
4
|
+
module Services
|
|
5
|
+
# Token lookup accessors for TokenStore
|
|
6
|
+
module TokenLookup
|
|
7
|
+
# Get the skype_spaces_token for refresh
|
|
8
|
+
def skype_spaces_token = load_tokens['skype_spaces_token']
|
|
9
|
+
|
|
10
|
+
# Get OIDC refresh credentials
|
|
11
|
+
def refresh_token = load_tokens['refresh_token']
|
|
12
|
+
def client_id = load_tokens['client_id']
|
|
13
|
+
def tenant_id = load_tokens['tenant_id']
|
|
14
|
+
|
|
15
|
+
def configured?
|
|
16
|
+
File.exist?(tokens_file) && !load_tokens.empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def token_age
|
|
20
|
+
return nil unless File.exist?(tokens_file)
|
|
21
|
+
|
|
22
|
+
tokens = load_tokens
|
|
23
|
+
timestamp = tokens['tokens_refreshed_at'] || tokens['saved_at']
|
|
24
|
+
timestamp ? Time.now - Time.parse(timestamp) : nil
|
|
25
|
+
rescue ArgumentError
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Manages Teams authentication tokens stored in config directory
|
|
31
|
+
class TokenStore
|
|
32
|
+
include TokenLookup
|
|
33
|
+
|
|
34
|
+
TOKENS_FILE = 'tokens.json'
|
|
35
|
+
|
|
36
|
+
def initialize(paths: Support::XdgPaths.new)
|
|
37
|
+
@paths = paths
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def account
|
|
41
|
+
auth, skype, name, chatsvc, presence =
|
|
42
|
+
load_tokens.values_at('auth_token', 'skype_token', 'name', 'chatsvc_token', 'skype_spaces_token')
|
|
43
|
+
return nil unless auth && skype
|
|
44
|
+
|
|
45
|
+
Models::Account.new(
|
|
46
|
+
name: name || 'default', auth_token: auth,
|
|
47
|
+
skype_token: skype, chatsvc_token: chatsvc, presence_token: presence
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def save(**tokens)
|
|
52
|
+
@paths.ensure_config_dir
|
|
53
|
+
data = tokens.transform_keys(&:to_s).merge('saved_at' => Time.now.iso8601).compact
|
|
54
|
+
write_token_file(data)
|
|
55
|
+
rescue SystemCallError, IOError => e
|
|
56
|
+
warn "teems: Could not save tokens: #{e.message}"
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Update just the skype_token (used during refresh)
|
|
61
|
+
def update_skype_token(skype_token)
|
|
62
|
+
with_loaded_tokens { |data| apply_skype_token(data, skype_token) }
|
|
63
|
+
rescue SystemCallError, IOError => e
|
|
64
|
+
warn "teems: Could not update token file: #{e.message}"
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Update all tokens at once (used by OIDC refresh)
|
|
69
|
+
def update_all_tokens(**tokens)
|
|
70
|
+
with_loaded_tokens { |data| apply_all_tokens(data, tokens) }
|
|
71
|
+
rescue SystemCallError, IOError => e
|
|
72
|
+
warn "teems: Could not update token file: #{e.message}"
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clear = FileUtils.rm_f(tokens_file)
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def with_loaded_tokens
|
|
81
|
+
data = load_tokens
|
|
82
|
+
return false if data.empty?
|
|
83
|
+
|
|
84
|
+
yield data
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def tokens_file
|
|
88
|
+
@paths.config_file(TOKENS_FILE)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def apply_skype_token(data, skype_token)
|
|
92
|
+
data.merge!('skype_token' => skype_token, 'skype_token_refreshed_at' => Time.now.iso8601)
|
|
93
|
+
write_token_file(data)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def apply_all_tokens(data, tokens)
|
|
97
|
+
updates = tokens.compact.transform_keys(&:to_s).merge('tokens_refreshed_at' => Time.now.iso8601)
|
|
98
|
+
write_token_file(data.merge!(updates))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def write_token_file(data)
|
|
102
|
+
File.write(tokens_file, JSON.pretty_generate(data))
|
|
103
|
+
File.chmod(0o600, tokens_file)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def load_tokens
|
|
107
|
+
return {} unless File.exist?(tokens_file)
|
|
108
|
+
|
|
109
|
+
JSON.parse(File.read(tokens_file))
|
|
110
|
+
rescue JSON::ParserError => e
|
|
111
|
+
warn "teems: Token file corrupted (#{e.message}), please re-authenticate with: teems auth login"
|
|
112
|
+
{}
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teems
|
|
4
|
+
module Support
|
|
5
|
+
# Logs errors to a file for debugging
|
|
6
|
+
module ErrorLogger
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def log(error, paths: XdgPaths.new)
|
|
10
|
+
log_file = prepare_log_file(paths)
|
|
11
|
+
append_error_entry(log_file, error)
|
|
12
|
+
rescue SystemCallError, IOError => e
|
|
13
|
+
warn "teems: Could not write error log: #{e.message}"
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def append_error_entry(log_file, error)
|
|
18
|
+
File.open(log_file, 'a') { |file| write_entry(file, error) }
|
|
19
|
+
log_file
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def prepare_log_file(paths)
|
|
23
|
+
paths.ensure_cache_dir
|
|
24
|
+
paths.cache_file('error.log')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def write_entry(file, error)
|
|
28
|
+
file.puts "#{Time.now.iso8601} - #{error.class}: #{error.message}"
|
|
29
|
+
backtrace = error.backtrace
|
|
30
|
+
file.puts backtrace.first(10).map { |line| " #{line}" }.join("\n") if backtrace
|
|
31
|
+
file.puts
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teems
|
|
4
|
+
module Support
|
|
5
|
+
# Formats help text for commands
|
|
6
|
+
class HelpFormatter
|
|
7
|
+
def initialize(usage)
|
|
8
|
+
@usage = usage
|
|
9
|
+
@description = nil
|
|
10
|
+
@sections = []
|
|
11
|
+
@notes = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def description(text)
|
|
15
|
+
@description = text
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def note(text)
|
|
19
|
+
@notes << text
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def section(title)
|
|
23
|
+
section = Section.new(title)
|
|
24
|
+
yield section
|
|
25
|
+
@sections << section
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def render
|
|
29
|
+
lines = build_help_lines
|
|
30
|
+
@notes.each { |note| lines << "Note: #{note}" }
|
|
31
|
+
lines.join("\n")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build_help_lines
|
|
35
|
+
lines = [@usage, '']
|
|
36
|
+
lines.push(@description, '') if @description
|
|
37
|
+
@sections.each { |section| lines.push(section.render, '') }
|
|
38
|
+
lines
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Section within help text
|
|
42
|
+
class Section
|
|
43
|
+
ITEM_RENDERERS = {
|
|
44
|
+
option: ->(args) { " #{args[0].ljust(20)} #{args[1]}" },
|
|
45
|
+
item: ->(args) { " #{args[0].ljust(20)} #{args[1]}" },
|
|
46
|
+
text: ->(args) { " #{args.first}" }
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
def initialize(title)
|
|
50
|
+
@title = title
|
|
51
|
+
@items = []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def option(flags, description)
|
|
55
|
+
@items << [:option, flags, description]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def item(label, description)
|
|
59
|
+
@items << [:item, label, description]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def text(content)
|
|
63
|
+
@items << [:text, content]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def render
|
|
67
|
+
lines = ["#{@title}:"]
|
|
68
|
+
@items.each { |type, *args| lines << render_item(type, args) }
|
|
69
|
+
lines.join("\n")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def render_item(type, args)
|
|
75
|
+
ITEM_RENDERERS[type]&.call(args)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teems
|
|
4
|
+
module Support
|
|
5
|
+
# Shared timezone detection logic for calendar and schedule commands
|
|
6
|
+
module Timezone
|
|
7
|
+
TIMEZONE_MAP = {
|
|
8
|
+
'EST' => 'America/New_York', 'EDT' => 'America/New_York',
|
|
9
|
+
'CST' => 'America/Chicago', 'CDT' => 'America/Chicago',
|
|
10
|
+
'MST' => 'America/Denver', 'MDT' => 'America/Denver',
|
|
11
|
+
'PST' => 'America/Los_Angeles', 'PDT' => 'America/Los_Angeles',
|
|
12
|
+
'AKST' => 'America/Anchorage', 'AKDT' => 'America/Anchorage',
|
|
13
|
+
'HST' => 'Pacific/Honolulu', 'UTC' => 'UTC', 'GMT' => 'UTC'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def detect_timezone
|
|
17
|
+
tz_from_env || tz_from_system
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def short_tz_label
|
|
21
|
+
abbrev = tz_from_system_abbrev
|
|
22
|
+
abbrev.gsub(/[DS](?=T$)/, '')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def tz_from_system_abbrev
|
|
28
|
+
Time.now.strftime('%Z')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def tz_from_env
|
|
32
|
+
tz_env = ENV.fetch('TZ', nil)
|
|
33
|
+
return nil if tz_env.to_s.empty?
|
|
34
|
+
return tz_env if tz_env.include?('/')
|
|
35
|
+
|
|
36
|
+
TIMEZONE_MAP[tz_env]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def tz_from_system
|
|
40
|
+
TIMEZONE_MAP[Time.now.strftime('%Z')] || 'UTC'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teems
|
|
4
|
+
module Support
|
|
5
|
+
# XDG-compliant paths for config, cache, and data directories
|
|
6
|
+
class XdgPaths
|
|
7
|
+
def config_dir
|
|
8
|
+
@config_dir ||= File.join(
|
|
9
|
+
ENV.fetch('XDG_CONFIG_HOME', File.join(Dir.home, '.config')),
|
|
10
|
+
'teems'
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def cache_dir
|
|
15
|
+
@cache_dir ||= File.join(
|
|
16
|
+
ENV.fetch('XDG_CACHE_HOME', File.join(Dir.home, '.cache')),
|
|
17
|
+
'teems'
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def data_dir
|
|
22
|
+
@data_dir ||= File.join(
|
|
23
|
+
ENV.fetch('XDG_DATA_HOME', File.join(Dir.home, '.local', 'share')),
|
|
24
|
+
'teems'
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def config_file(filename)
|
|
29
|
+
File.join(config_dir, filename)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def cache_file(filename)
|
|
33
|
+
File.join(cache_dir, filename)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def data_file(filename)
|
|
37
|
+
File.join(data_dir, filename)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ensure_config_dir
|
|
41
|
+
FileUtils.mkdir_p(config_dir)
|
|
42
|
+
rescue SystemCallError => e
|
|
43
|
+
warn "teems: Could not create config directory #{config_dir}: #{e.message}"
|
|
44
|
+
raise
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def ensure_cache_dir
|
|
48
|
+
FileUtils.mkdir_p(cache_dir)
|
|
49
|
+
rescue SystemCallError => e
|
|
50
|
+
warn "teems: Could not create cache directory #{cache_dir}: #{e.message}"
|
|
51
|
+
raise
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ensure_data_dir
|
|
55
|
+
FileUtils.mkdir_p(data_dir)
|
|
56
|
+
rescue SystemCallError => e
|
|
57
|
+
warn "teems: Could not create data directory #{data_dir}: #{e.message}"
|
|
58
|
+
raise
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/teems.rb
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'optparse'
|
|
8
|
+
require 'time'
|
|
9
|
+
require 'io/console'
|
|
10
|
+
require 'digest'
|
|
11
|
+
|
|
12
|
+
# Microsoft Teams CLI - A command-line interface for Microsoft Teams
|
|
13
|
+
module Teems
|
|
14
|
+
# Base error class for all Teems errors
|
|
15
|
+
class Error < StandardError; end
|
|
16
|
+
|
|
17
|
+
# Error raised when a Teams API request fails (4xx/5xx responses)
|
|
18
|
+
class ApiError < Error
|
|
19
|
+
attr_reader :status_code
|
|
20
|
+
|
|
21
|
+
def initialize(message = nil, status_code: nil)
|
|
22
|
+
@status_code = status_code
|
|
23
|
+
super(message)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def not_found? = status_code == 404
|
|
27
|
+
def unauthorized? = status_code == 401
|
|
28
|
+
def forbidden? = status_code == 403
|
|
29
|
+
def rate_limited? = status_code == 429
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Error raised when required configuration is missing or invalid
|
|
33
|
+
class ConfigError < Error; end
|
|
34
|
+
|
|
35
|
+
# Error raised when authentication fails or tokens are missing
|
|
36
|
+
class AuthError < Error; end
|
|
37
|
+
|
|
38
|
+
# Error raised when token storage operations fail
|
|
39
|
+
class TokenStoreError < Error; end
|
|
40
|
+
|
|
41
|
+
autoload :VERSION, 'teems/version'
|
|
42
|
+
autoload :CLI, 'teems/cli'
|
|
43
|
+
autoload :Runner, 'teems/runner'
|
|
44
|
+
|
|
45
|
+
# Data models for Teams entities
|
|
46
|
+
module Models
|
|
47
|
+
autoload :Account, 'teems/models/account'
|
|
48
|
+
autoload :Channel, 'teems/models/channel'
|
|
49
|
+
autoload :Chat, 'teems/models/chat'
|
|
50
|
+
autoload :Event, 'teems/models/event'
|
|
51
|
+
autoload :Message, 'teems/models/message'
|
|
52
|
+
autoload :Parsing, 'teems/models/parsing'
|
|
53
|
+
autoload :User, 'teems/models/user'
|
|
54
|
+
autoload :UserProfile, 'teems/models/user_profile'
|
|
55
|
+
autoload :Duration, 'teems/models/duration'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Application services for configuration, caching, and API communication
|
|
59
|
+
module Services
|
|
60
|
+
autoload :ApiClient, 'teems/services/api_client'
|
|
61
|
+
autoload :Configuration, 'teems/services/configuration'
|
|
62
|
+
autoload :TokenStore, 'teems/services/token_store'
|
|
63
|
+
autoload :TokenExtractor, 'teems/services/token_extractor'
|
|
64
|
+
autoload :TokenRefresher, 'teems/services/token_refresher'
|
|
65
|
+
autoload :CacheStore, 'teems/services/cache_store'
|
|
66
|
+
autoload :FileDownloader, 'teems/services/file_downloader'
|
|
67
|
+
autoload :TeamsUrlParser, 'teems/services/teams_url_parser'
|
|
68
|
+
autoload :SyncStore, 'teems/services/sync_store'
|
|
69
|
+
autoload :SyncDirNaming, 'teems/services/sync_dir_naming'
|
|
70
|
+
autoload :SyncEngine, 'teems/services/sync_engine'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Output formatters for messages and terminal output
|
|
74
|
+
module Formatters
|
|
75
|
+
autoload :Output, 'teems/formatters/output'
|
|
76
|
+
autoload :FormatUtils, 'teems/formatters/format_utils'
|
|
77
|
+
autoload :MessageFormatter, 'teems/formatters/message_formatter'
|
|
78
|
+
autoload :MarkdownFormatter, 'teems/formatters/markdown_formatter'
|
|
79
|
+
autoload :CalendarFormatter, 'teems/formatters/calendar_formatter'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# CLI commands implementing user-facing functionality
|
|
83
|
+
module Commands
|
|
84
|
+
autoload :Base, 'teems/commands/base'
|
|
85
|
+
autoload :Activity, 'teems/commands/activity'
|
|
86
|
+
autoload :Auth, 'teems/commands/auth'
|
|
87
|
+
autoload :Cal, 'teems/commands/cal'
|
|
88
|
+
autoload :Channels, 'teems/commands/channels'
|
|
89
|
+
autoload :Chats, 'teems/commands/chats'
|
|
90
|
+
autoload :Messages, 'teems/commands/messages'
|
|
91
|
+
autoload :Sync, 'teems/commands/sync'
|
|
92
|
+
autoload :Help, 'teems/commands/help'
|
|
93
|
+
autoload :Who, 'teems/commands/who'
|
|
94
|
+
autoload :Ooo, 'teems/commands/ooo'
|
|
95
|
+
autoload :Org, 'teems/commands/org'
|
|
96
|
+
autoload :Status, 'teems/commands/status'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Thin wrappers around Teams API endpoints
|
|
100
|
+
module Api
|
|
101
|
+
autoload :Client, 'teems/api/client'
|
|
102
|
+
autoload :Calendar, 'teems/api/calendar'
|
|
103
|
+
autoload :Channels, 'teems/api/channels'
|
|
104
|
+
autoload :Chats, 'teems/api/chats'
|
|
105
|
+
autoload :Files, 'teems/api/files'
|
|
106
|
+
autoload :Messages, 'teems/api/messages'
|
|
107
|
+
autoload :Users, 'teems/api/users'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Utility classes for paths and helpers
|
|
111
|
+
module Support
|
|
112
|
+
autoload :XdgPaths, 'teems/support/xdg_paths'
|
|
113
|
+
autoload :HelpFormatter, 'teems/support/help_formatter'
|
|
114
|
+
autoload :ErrorLogger, 'teems/support/error_logger'
|
|
115
|
+
autoload :Timezone, 'teems/support/timezone'
|
|
116
|
+
end
|
|
117
|
+
end
|