teems 0.1.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +83 -39
- data/lib/teems/api/client.rb +1 -2
- data/lib/teems/api/meetings.rb +25 -0
- data/lib/teems/cli.rb +5 -1
- data/lib/teems/commands/base.rb +15 -2
- data/lib/teems/commands/cal.rb +1 -51
- data/lib/teems/commands/help.rb +1 -0
- data/lib/teems/commands/meeting.rb +488 -0
- data/lib/teems/commands/meeting_recording.rb +331 -0
- data/lib/teems/commands/meeting_transcript.rb +270 -0
- data/lib/teems/commands/ooo.rb +123 -47
- data/lib/teems/runner.rb +6 -0
- data/lib/teems/services/api_client.rb +3 -3
- data/lib/teems/services/safari_js_runner.rb +89 -0
- data/lib/teems/support/time_parsing.rb +55 -0
- data/lib/teems/version.rb +1 -1
- data/lib/teems.rb +4 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6b2001d1f6e7653df296a0443a0a2f2bec80068ca05b4693bfd2b74c5f6a5240
|
|
4
|
+
data.tar.gz: 4f8e3920b836feb38237cd828a11ab63589855391f21cbea90eee6a54a34be9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 245ff54545cf04f3d0db0062ab51ae2e1dc001a474c45c05e7e7cde21e8a00a523813b5019b96acaba0665010b6a6d31b7922141cb01a4203a2850f10fe5ad56
|
|
7
|
+
data.tar.gz: dffaa23979af2e85fffb8b0d3b8b3d8688aee1ff7c6d53eeeb5c151c81836380e93921eb5c37e2f2acf7d633a2f73e02a007e0c70af59a01e68f0bcb31a87f99
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-04-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `teems meeting --audio` - Download audio-only M4A alongside or instead of video (ideal for transcription)
|
|
7
|
+
- `teems meeting --no-video` - Skip the video download for audio-only output
|
|
8
|
+
- `teems ooo` now supports timed schedules via `--start`/`--end` (e.g., "today 14:00") in addition to all-day dates
|
|
9
|
+
- `teems ooo --invite` - Override the configured notify list for a single invocation
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Recording, audio, and transcript files share a common base name derived from the SharePoint file (e.g., `2026-01-20 - Team Sync.mp4`/`.m4a`/`.vtt`)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- `teems meeting` shows call duration once on the event header instead of repeating it per participant
|
|
16
|
+
|
|
17
|
+
## [0.2.0] - 2026-04-14
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- `teems meeting` - View meeting details, download transcripts, and download recordings
|
|
21
|
+
- Auto-authentication when tokens are missing or expired (no manual `auth login` needed)
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- `teems cal delete` now uses configured endpoints instead of hardcoded defaults
|
|
25
|
+
|
|
3
26
|
## [0.1.0] - 2026-04-10
|
|
4
27
|
|
|
5
28
|
### Added
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# teems
|
|
2
2
|
|
|
3
|
-
A command-line interface for Microsoft Teams. Read messages,
|
|
3
|
+
A command-line interface for Microsoft Teams. Read messages, manage your calendar, set out-of-office, and more — all from the terminal.
|
|
4
4
|
|
|
5
|
-
Pure Ruby, no dependencies.
|
|
5
|
+
Pure Ruby, no runtime dependencies. macOS only (uses Safari for authentication).
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -22,63 +22,98 @@ gem install teems-*.gem
|
|
|
22
22
|
## Requirements
|
|
23
23
|
|
|
24
24
|
- Ruby 3.2+
|
|
25
|
-
- macOS (for Safari token extraction)
|
|
25
|
+
- macOS (for Safari/WKWebView token extraction)
|
|
26
26
|
- Microsoft Teams account
|
|
27
27
|
|
|
28
28
|
## Authentication
|
|
29
29
|
|
|
30
|
-
teems requires authentication tokens from Teams. The easiest way is Safari automation:
|
|
31
|
-
|
|
32
30
|
```bash
|
|
33
|
-
teems auth login
|
|
31
|
+
teems auth login # Authenticate (headless or Safari)
|
|
32
|
+
teems auth status # Check if authenticated
|
|
33
|
+
teems auth logout # Clear stored tokens
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Tokens refresh automatically when you run commands. If they expire (~24 hours of inactivity), just run `teems auth login` again.
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
### Calendar
|
|
39
41
|
|
|
40
42
|
```bash
|
|
41
|
-
teems
|
|
43
|
+
teems cal # Today's events
|
|
44
|
+
teems cal tomorrow # Tomorrow's events
|
|
45
|
+
teems cal --week # This week's events
|
|
46
|
+
teems cal show 3 # View details for event #3
|
|
47
|
+
teems cal accept 3 # Accept event #3
|
|
48
|
+
teems cal decline 3 # Decline event #3
|
|
49
|
+
teems cal create "Standup" --start "tomorrow 09:00" --attendees alice@example.com
|
|
50
|
+
teems cal delete 3 # Delete event #3
|
|
42
51
|
```
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
### Messages
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
teems messages <chat-id> # Read from a chat
|
|
57
|
+
teems messages <channel-id> -t <team-id> # Read from a channel
|
|
58
|
+
teems messages <chat-id> -n 50 # Show more messages
|
|
59
|
+
```
|
|
45
60
|
|
|
46
|
-
###
|
|
61
|
+
### Channels and Chats
|
|
47
62
|
|
|
48
63
|
```bash
|
|
49
|
-
teems channels
|
|
64
|
+
teems channels # List joined teams and channels
|
|
65
|
+
teems chats # List recent chats
|
|
66
|
+
teems chats -n 50 # Show 50 chats
|
|
50
67
|
```
|
|
51
68
|
|
|
52
|
-
###
|
|
69
|
+
### Out of Office
|
|
53
70
|
|
|
54
71
|
```bash
|
|
55
|
-
teems
|
|
56
|
-
teems
|
|
72
|
+
teems ooo # Check OOO status
|
|
73
|
+
teems ooo on # Enable OOO (auto-reply + presence)
|
|
74
|
+
teems ooo on --message "Vacation" # Custom message
|
|
75
|
+
teems ooo on --start 2025-12-22 --end 2025-12-26 # Scheduled
|
|
76
|
+
teems ooo on --event # Also create a calendar event for your notify list
|
|
77
|
+
teems ooo off # Disable OOO
|
|
78
|
+
teems ooo config # Show OOO configuration
|
|
57
79
|
```
|
|
58
80
|
|
|
59
|
-
###
|
|
81
|
+
### Meetings
|
|
60
82
|
|
|
61
83
|
```bash
|
|
62
|
-
#
|
|
63
|
-
teems
|
|
84
|
+
teems meeting <thread-id> # View meeting summary
|
|
85
|
+
teems meeting <thread-id> --chat # Show meeting chat
|
|
86
|
+
teems meeting <thread-id> --transcript -o ~/Downloads # Download transcript (VTT)
|
|
87
|
+
teems meeting <thread-id> --recording -o ~/Downloads # Download recording (MP4)
|
|
88
|
+
teems meeting <thread-id> --recording --transcript -o ~/Downloads # Both, with embedded subtitles
|
|
89
|
+
teems meeting <event-id> # By calendar event ID (AAMk...)
|
|
90
|
+
teems meeting "https://teams.microsoft.com/..." # By Teams URL or recap link
|
|
91
|
+
```
|
|
64
92
|
|
|
65
|
-
|
|
66
|
-
teems messages <channel-id> -t <team-id>
|
|
93
|
+
Recording download requires `ffmpeg` (`brew install ffmpeg`) and downloads via DASH streaming with 5 parallel threads. No browser required.
|
|
67
94
|
|
|
68
|
-
|
|
69
|
-
|
|
95
|
+
### People
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
teems who # Show your profile
|
|
99
|
+
teems who john # Search for a user
|
|
100
|
+
teems org # Show your org chart
|
|
101
|
+
teems org john # Org chart for "john"
|
|
70
102
|
```
|
|
71
103
|
|
|
72
|
-
###
|
|
104
|
+
### Status and Activity
|
|
73
105
|
|
|
74
106
|
```bash
|
|
75
|
-
teems
|
|
107
|
+
teems status # Show your presence
|
|
108
|
+
teems status --presence available # Set presence
|
|
109
|
+
teems status --message "In a meeting" # Set status message
|
|
110
|
+
teems activity # Show activity feed
|
|
76
111
|
```
|
|
77
112
|
|
|
78
|
-
###
|
|
113
|
+
### Sync
|
|
79
114
|
|
|
80
115
|
```bash
|
|
81
|
-
teems
|
|
116
|
+
teems sync # Sync chat history locally
|
|
82
117
|
```
|
|
83
118
|
|
|
84
119
|
## Global Options
|
|
@@ -95,12 +130,29 @@ teems auth logout
|
|
|
95
130
|
|
|
96
131
|
Configuration is stored in XDG-compliant directories:
|
|
97
132
|
|
|
98
|
-
- Config: `~/.config/teems
|
|
133
|
+
- Config: `~/.config/teems/config.json`
|
|
134
|
+
- Tokens: `~/.config/teems/tokens.json`
|
|
99
135
|
- Cache: `~/.cache/teems/`
|
|
100
136
|
|
|
137
|
+
### OOO Defaults
|
|
138
|
+
|
|
139
|
+
Set default messages and a notify list for the `ooo` command:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"ooo": {
|
|
144
|
+
"internal_message": "I'm currently out of office.",
|
|
145
|
+
"external_message": "Thank you for your message. I'm out of office.",
|
|
146
|
+
"external_audience": "all",
|
|
147
|
+
"status_message": "Out of Office",
|
|
148
|
+
"notify": ["manager@example.com", "team@example.com"]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
101
153
|
### Custom Endpoints
|
|
102
154
|
|
|
103
|
-
By default, teems connects to commercial Microsoft Teams endpoints. To use a different environment (e.g., GCC, GCC High), add an `endpoints` section to
|
|
155
|
+
By default, teems connects to commercial Microsoft Teams endpoints. To use a different environment (e.g., GCC, GCC High), add an `endpoints` section to your config:
|
|
104
156
|
|
|
105
157
|
```json
|
|
106
158
|
{
|
|
@@ -113,22 +165,14 @@ By default, teems connects to commercial Microsoft Teams endpoints. To use a dif
|
|
|
113
165
|
|
|
114
166
|
Available endpoint keys: `graph`, `teams`, `msgservice`, `presence`.
|
|
115
167
|
|
|
116
|
-
## Token Expiration
|
|
117
|
-
|
|
118
|
-
Teams tokens expire after ~24 hours. When you see authentication errors, run:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
teems auth login
|
|
122
|
-
```
|
|
123
|
-
|
|
124
168
|
## Development
|
|
125
169
|
|
|
126
170
|
```bash
|
|
127
171
|
git clone https://github.com/ericboehs/teems
|
|
128
172
|
cd teems
|
|
129
|
-
|
|
130
|
-
rake test
|
|
131
|
-
rake console
|
|
173
|
+
bundle install
|
|
174
|
+
rake test # Run tests
|
|
175
|
+
rake console # Interactive console
|
|
132
176
|
```
|
|
133
177
|
|
|
134
178
|
## License
|
data/lib/teems/api/client.rb
CHANGED
|
@@ -32,8 +32,7 @@ module Teems
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def delete(path)
|
|
35
|
-
|
|
36
|
-
@api.delete("#{base_url}#{path}", endpoint_key: endpoint, account: @account)
|
|
35
|
+
@api.delete(endpoint, path, account: @account)
|
|
37
36
|
end
|
|
38
37
|
end
|
|
39
38
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teems
|
|
4
|
+
module Api
|
|
5
|
+
# API wrapper for meeting-related endpoints (Graph shares API)
|
|
6
|
+
class Meetings < Client
|
|
7
|
+
def share_preview(sharing_url)
|
|
8
|
+
encoded = encode_sharing_url(sharing_url)
|
|
9
|
+
post("/v1.0/shares/u!#{encoded}/driveItem/preview")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def share_item(sharing_url, select: nil)
|
|
13
|
+
encoded = encode_sharing_url(sharing_url)
|
|
14
|
+
params = select ? { '$select' => select } : {}
|
|
15
|
+
get("/v1.0/shares/u!#{encoded}/driveItem", params: params)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def encode_sharing_url(url)
|
|
21
|
+
[url].pack('m0').tr('+/', '-_').delete('=')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/teems/cli.rb
CHANGED
|
@@ -8,6 +8,8 @@ module Teems
|
|
|
8
8
|
def verbose_mode? = @argv.include?('-v') || @argv.include?('--verbose')
|
|
9
9
|
|
|
10
10
|
def build_runner
|
|
11
|
+
return @runner_override if @runner_override
|
|
12
|
+
|
|
11
13
|
out = verbose_mode? ? @output.with_verbose : @output
|
|
12
14
|
Runner.new(output: out).tap { |new_runner| setup_verbose_logging(new_runner, out) if out.verbose? }
|
|
13
15
|
end
|
|
@@ -45,6 +47,7 @@ module Teems
|
|
|
45
47
|
'cal' => Commands::Cal,
|
|
46
48
|
'channels' => Commands::Channels,
|
|
47
49
|
'chats' => Commands::Chats,
|
|
50
|
+
'meeting' => Commands::Meeting,
|
|
48
51
|
'messages' => Commands::Messages,
|
|
49
52
|
'sync' => Commands::Sync,
|
|
50
53
|
'who' => Commands::Who,
|
|
@@ -54,9 +57,10 @@ module Teems
|
|
|
54
57
|
'help' => Commands::Help
|
|
55
58
|
}.freeze
|
|
56
59
|
|
|
57
|
-
def initialize(argv, output: Formatters::Output.new)
|
|
60
|
+
def initialize(argv, output: Formatters::Output.new, runner: nil)
|
|
58
61
|
@argv = argv.dup
|
|
59
62
|
@output = output
|
|
63
|
+
@runner_override = runner
|
|
60
64
|
end
|
|
61
65
|
|
|
62
66
|
def run
|
data/lib/teems/commands/base.rb
CHANGED
|
@@ -119,6 +119,7 @@ module Teems
|
|
|
119
119
|
|
|
120
120
|
def require_auth
|
|
121
121
|
return nil if runner.configured?
|
|
122
|
+
return nil if auto_login
|
|
122
123
|
|
|
123
124
|
error('Not authenticated. Run: teems auth login')
|
|
124
125
|
1
|
|
@@ -137,9 +138,21 @@ module Teems
|
|
|
137
138
|
|
|
138
139
|
def attempt_token_refresh
|
|
139
140
|
debug('Token expired, attempting refresh...')
|
|
140
|
-
|
|
141
|
+
return debug('Token refreshed, retrying request...') if runner.refresh_tokens
|
|
141
142
|
|
|
142
|
-
debug('Token
|
|
143
|
+
debug('Token refresh failed, re-authenticating...')
|
|
144
|
+
raise ApiError, 'Re-authentication failed' unless auto_login
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def auto_login
|
|
148
|
+
tokens = runner.token_extractor.extract
|
|
149
|
+
return unless tokens&.[](:auth_token) && tokens[:skype_token]
|
|
150
|
+
|
|
151
|
+
runner.token_store.save(**tokens)
|
|
152
|
+
true
|
|
153
|
+
rescue Teems::Error, IOError, SystemCallError => e
|
|
154
|
+
debug("Auto-login failed: #{e.message}")
|
|
155
|
+
nil
|
|
143
156
|
end
|
|
144
157
|
end
|
|
145
158
|
end
|
data/lib/teems/commands/cal.rb
CHANGED
|
@@ -477,56 +477,6 @@ module Teems
|
|
|
477
477
|
end
|
|
478
478
|
end
|
|
479
479
|
|
|
480
|
-
# Time parsing for create subcommand
|
|
481
|
-
module CalTimeParsing
|
|
482
|
-
private
|
|
483
|
-
|
|
484
|
-
def parse_time_input(raw)
|
|
485
|
-
date, time_str = split_time_input(raw)
|
|
486
|
-
return unless time_str
|
|
487
|
-
|
|
488
|
-
date.is_a?(String) ? parse_absolute_time(date, time_str) : parse_relative_time(date, time_str)
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
def split_time_input(raw)
|
|
492
|
-
base = Date.today
|
|
493
|
-
split_tomorrow_time(raw, base) || split_today_time(raw, base) || split_absolute_time(raw)
|
|
494
|
-
end
|
|
495
|
-
|
|
496
|
-
def split_tomorrow_time(raw, base)
|
|
497
|
-
return unless raw.start_with?('tomorrow ')
|
|
498
|
-
|
|
499
|
-
[base + 1, raw.delete_prefix('tomorrow ')]
|
|
500
|
-
end
|
|
501
|
-
|
|
502
|
-
def split_today_time(raw, base)
|
|
503
|
-
return unless raw.match?(/\A(?:today\s+)?\d{1,2}:\d{2}\z/)
|
|
504
|
-
|
|
505
|
-
[base, raw.delete_prefix('today ')]
|
|
506
|
-
end
|
|
507
|
-
|
|
508
|
-
def split_absolute_time(raw)
|
|
509
|
-
return unless raw.match?(/\A\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\z/)
|
|
510
|
-
|
|
511
|
-
raw.split(/\s+/, 2)
|
|
512
|
-
end
|
|
513
|
-
|
|
514
|
-
def parse_relative_time(date, time_str)
|
|
515
|
-
hour, min = time_str.split(':').map(&:to_i)
|
|
516
|
-
debug("Parsing time #{hour}:#{min} for #{@options[:date]}") if @options[:verbose]
|
|
517
|
-
Time.new(date.year, date.month, date.day, hour, min, 0)
|
|
518
|
-
rescue ArgumentError
|
|
519
|
-
nil
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
def parse_absolute_time(date_str, time_str)
|
|
523
|
-
date = Date.parse(date_str)
|
|
524
|
-
parse_relative_time(date, time_str)
|
|
525
|
-
rescue Date::Error
|
|
526
|
-
nil
|
|
527
|
-
end
|
|
528
|
-
end
|
|
529
|
-
|
|
530
480
|
# Builds the event body hash fields for create requests
|
|
531
481
|
module CalEventFields
|
|
532
482
|
private
|
|
@@ -640,7 +590,7 @@ module Teems
|
|
|
640
590
|
|
|
641
591
|
# Event creation subcommand
|
|
642
592
|
module CalCreateActions
|
|
643
|
-
include
|
|
593
|
+
include Support::TimeParsing
|
|
644
594
|
include CalEventFields
|
|
645
595
|
include CalCreateValidation
|
|
646
596
|
|
data/lib/teems/commands/help.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Teems
|
|
|
8
8
|
['cal', 'List calendar events and view details'],
|
|
9
9
|
['channels', 'List joined teams and channels'],
|
|
10
10
|
['chats', 'List recent chats'],
|
|
11
|
+
['meeting', 'View meeting details, chat, transcripts, and recordings'],
|
|
11
12
|
['messages', 'Read messages from a channel or chat'],
|
|
12
13
|
['sync', 'Sync chat history locally'],
|
|
13
14
|
['who', "Look up a user's profile"],
|