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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +24 -0
  3. data/LICENSE +21 -0
  4. data/README.md +136 -0
  5. data/bin/teems +7 -0
  6. data/lib/teems/api/calendar.rb +94 -0
  7. data/lib/teems/api/channels.rb +26 -0
  8. data/lib/teems/api/chats.rb +29 -0
  9. data/lib/teems/api/client.rb +40 -0
  10. data/lib/teems/api/files.rb +12 -0
  11. data/lib/teems/api/messages.rb +58 -0
  12. data/lib/teems/api/users.rb +88 -0
  13. data/lib/teems/api/users_mailbox.rb +16 -0
  14. data/lib/teems/api/users_presence.rb +43 -0
  15. data/lib/teems/cli.rb +133 -0
  16. data/lib/teems/commands/activity.rb +222 -0
  17. data/lib/teems/commands/auth.rb +268 -0
  18. data/lib/teems/commands/base.rb +146 -0
  19. data/lib/teems/commands/cal.rb +891 -0
  20. data/lib/teems/commands/channels.rb +115 -0
  21. data/lib/teems/commands/chats.rb +159 -0
  22. data/lib/teems/commands/help.rb +107 -0
  23. data/lib/teems/commands/messages.rb +281 -0
  24. data/lib/teems/commands/ooo.rb +385 -0
  25. data/lib/teems/commands/org.rb +232 -0
  26. data/lib/teems/commands/status.rb +224 -0
  27. data/lib/teems/commands/sync.rb +390 -0
  28. data/lib/teems/commands/who.rb +377 -0
  29. data/lib/teems/formatters/calendar_formatter.rb +227 -0
  30. data/lib/teems/formatters/format_utils.rb +56 -0
  31. data/lib/teems/formatters/markdown_formatter.rb +113 -0
  32. data/lib/teems/formatters/message_formatter.rb +67 -0
  33. data/lib/teems/formatters/output.rb +105 -0
  34. data/lib/teems/models/account.rb +59 -0
  35. data/lib/teems/models/channel.rb +31 -0
  36. data/lib/teems/models/chat.rb +111 -0
  37. data/lib/teems/models/duration.rb +46 -0
  38. data/lib/teems/models/event.rb +124 -0
  39. data/lib/teems/models/message.rb +125 -0
  40. data/lib/teems/models/parsing.rb +56 -0
  41. data/lib/teems/models/user.rb +25 -0
  42. data/lib/teems/models/user_profile.rb +45 -0
  43. data/lib/teems/runner.rb +81 -0
  44. data/lib/teems/services/api_client.rb +217 -0
  45. data/lib/teems/services/cache_store.rb +32 -0
  46. data/lib/teems/services/configuration.rb +56 -0
  47. data/lib/teems/services/file_downloader.rb +39 -0
  48. data/lib/teems/services/headless_extract.rb +192 -0
  49. data/lib/teems/services/safari_oauth.rb +285 -0
  50. data/lib/teems/services/sync_dir_naming.rb +42 -0
  51. data/lib/teems/services/sync_engine.rb +194 -0
  52. data/lib/teems/services/sync_store.rb +193 -0
  53. data/lib/teems/services/teams_url_parser.rb +78 -0
  54. data/lib/teems/services/token_exchange_scripts.rb +56 -0
  55. data/lib/teems/services/token_extractor.rb +401 -0
  56. data/lib/teems/services/token_extractor_scripts.rb +116 -0
  57. data/lib/teems/services/token_refresher.rb +169 -0
  58. data/lib/teems/services/token_store.rb +116 -0
  59. data/lib/teems/support/error_logger.rb +35 -0
  60. data/lib/teems/support/help_formatter.rb +80 -0
  61. data/lib/teems/support/timezone.rb +44 -0
  62. data/lib/teems/support/xdg_paths.rb +62 -0
  63. data/lib/teems/version.rb +5 -0
  64. data/lib/teems.rb +117 -0
  65. data/support/token_helper.swift +485 -0
  66. metadata +110 -0
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teems
4
+ module Commands
5
+ # Output helpers for CLI commands: logging, printing, JSON
6
+ module CommandOutput
7
+ protected
8
+
9
+ def success(msg) = @options[:quiet] || output.success(msg)
10
+ def info(msg) = @options[:quiet] || output.info(msg)
11
+ def warn(message) = output.warn(message)
12
+ def error(message) = output.error(message) || 1
13
+ def debug(msg) = @options[:verbose] && output.debug(msg)
14
+ def puts(message = '') = @options[:quiet] || output.puts(message)
15
+ def print(msg) = @options[:quiet] || output.print(msg)
16
+ def output_json(data) = output.puts(JSON.pretty_generate(data))
17
+ end
18
+
19
+ # Option validation helpers for CLI commands
20
+ module CommandValidation
21
+ protected
22
+
23
+ def check_unknown_options
24
+ return nil if @unknown_options.empty?
25
+
26
+ error("Unknown option: #{@unknown_options.first}")
27
+ error('Run with --help for available options.')
28
+ 1
29
+ end
30
+
31
+ def unknown_options? = @unknown_options.any?
32
+ def show_help? = @options[:help]
33
+
34
+ def show_help
35
+ output.puts help_text
36
+ 0
37
+ end
38
+
39
+ def validate_options
40
+ return show_help if show_help?
41
+ return check_unknown_options if unknown_options?
42
+
43
+ nil
44
+ end
45
+ end
46
+
47
+ # Base class for all CLI commands with option parsing and output helpers
48
+ class Base
49
+ include CommandOutput
50
+ include CommandValidation
51
+
52
+ attr_reader :runner, :options, :positional_args
53
+
54
+ def initialize(args, runner:)
55
+ @runner = runner
56
+ @options = default_options
57
+ @unknown_options = []
58
+ @positional_args = []
59
+ parse_options(args)
60
+ end
61
+
62
+ def execute
63
+ raise NotImplementedError, 'Subclass must implement #execute'
64
+ end
65
+
66
+ protected
67
+
68
+ # Convenience accessors
69
+ def output = runner.output
70
+ def config = runner.config
71
+ def cache_store = runner.cache_store
72
+ def token_store = runner.token_store
73
+ def api_client = runner.api_client
74
+
75
+ def default_options
76
+ { verbose: false, quiet: false, json: false, limit: 20 }
77
+ end
78
+
79
+ def parse_options(args)
80
+ pending = args.dup
81
+ while pending.any?
82
+ arg = pending.shift
83
+ if arg.start_with?('-')
84
+ parse_single_option(arg, pending)
85
+ else
86
+ @positional_args << arg
87
+ end
88
+ end
89
+ @positional_args
90
+ end
91
+
92
+ COMMON_OPTIONS = {
93
+ '-v' => :verbose, '--verbose' => :verbose,
94
+ '-q' => :quiet, '--quiet' => :quiet,
95
+ '--json' => :json, '-h' => :help, '--help' => :help
96
+ }.freeze
97
+
98
+ LIMIT_FLAGS = %w[-n --limit].freeze
99
+
100
+ private
101
+
102
+ def parse_single_option(arg, pending)
103
+ return @options[:limit] = pending.shift.to_i if LIMIT_FLAGS.include?(arg)
104
+ return @options[COMMON_OPTIONS[arg]] = true if COMMON_OPTIONS.key?(arg)
105
+
106
+ handle_option(arg, pending)
107
+ end
108
+
109
+ protected
110
+
111
+ # Override in subclass to handle command-specific options
112
+ def handle_option(arg, _pending)
113
+ @unknown_options << arg
114
+ end
115
+
116
+ def help_text
117
+ 'No help available for this command.'
118
+ end
119
+
120
+ def require_auth
121
+ return nil if runner.configured?
122
+
123
+ error('Not authenticated. Run: teems auth login')
124
+ 1
125
+ end
126
+
127
+ # Execute a block with automatic token refresh on 401
128
+ # Usage: with_token_refresh { runner.messages_api.chat_messages(chat_id: id) }
129
+ def with_token_refresh
130
+ yield
131
+ rescue ApiError => e
132
+ raise unless e.unauthorized? || e.message.include?('expired')
133
+
134
+ attempt_token_refresh
135
+ yield
136
+ end
137
+
138
+ def attempt_token_refresh
139
+ debug('Token expired, attempting refresh...')
140
+ raise ApiError, 'Token refresh failed' unless runner.refresh_tokens
141
+
142
+ debug('Token refreshed, retrying request...')
143
+ end
144
+ end
145
+ end
146
+ end