Commit32be4260Recorded25 Mar 2026Repositorytube
Use sigil-oauth for YouTube and Twitch token management
Message
Update require-youtube-client and require-twitch-client with priority-based auth: direct token > OAuth stored tokens > API key. OAuth mode auto-refreshes expired tokens and persists new ones.
Add tube-config fields for YOUTUBECLIENTID/SECRET and TWITCHCLIENTSECRET. Add 'tube auth' CLI command for running the OAuth authorization flow (opens browser, stores tokens).
Add sigil-oauth as dependency. Update README with OAuth setup docs.
Note: tube has a pre-existing module dependency cycle that prevents building — this is not caused by the oauth integration.
Changed
README.md | 30 +++++++++++++++++++++++++++++-
dev-redirects.sgl | 5 ++++-
package.sgl | 7 ++++++-
src/tube/config.sgl | 34 ++++++++++++++++++++++++++--------
src/tube/main.sgl | 50 +++++++++++++++++++++++++++++++++++++++++++++++++-
src/tube/twitch.sgl | 44 ++++++++++++++++++++++++++++++++++++++------
src/tube/youtube.sgl | 49 +++++++++++++++++++++++++++++++++++++++++++------
7 files changed, 195 insertions(+), 24 deletions(-)Diff
README.mdmodified
@@ -71,7 +71,26 @@ This starts an MCP server exposing the following tools:
71
72
## Configuration 73
−74
Set these environment variables:+74
### OAuth Mode (Recommended)+75
+76
Set client credentials and use `tube auth` to authorize:+77
+78
```bash+79
export YOUTUBE_CLIENT_ID="your-google-client-id"+80
export YOUTUBE_CLIENT_SECRET="your-google-client-secret"+81
export TWITCH_CLIENT_ID="your-twitch-client-id"+82
export TWITCH_CLIENT_SECRET="your-twitch-client-secret"+83
+84
# Authorize each platform (opens browser, stores tokens)+85
tube auth youtube+86
tube auth twitch+87
```+88
+89
Tokens are stored in `~/.config/sigil/<service>/tokens.json` and automatically refreshed when expired.+90
+91
### Direct Token Mode (Legacy)+92
+93
For manual token management, set access tokens directly: 94
95
| Variable | Required | Description | 96
|----------|----------|-------------|@@ -81,6 +100,15 @@ Set these environment variables:
100
| `TWITCH_ACCESS_TOKEN` | For Twitch | Twitch OAuth2 access token | 101
| `TUBE_DEFAULT_PLATFORM` | No | Default platform: `youtube`, `twitch`, or `both` (default: `both`) | 102
+103
### OAuth Environment Variables+104
+105
| Variable | Description |+106
|----------|-------------|+107
| `YOUTUBE_CLIENT_ID` | Google Cloud OAuth2 client ID |+108
| `YOUTUBE_CLIENT_SECRET` | Google Cloud OAuth2 client secret |+109
| `TWITCH_CLIENT_ID` | Twitch application client ID |+110
| `TWITCH_CLIENT_SECRET` | Twitch application client secret |+111
112
You only need credentials for the platforms you want to use. If you only have YouTube credentials, Twitch operations will gracefully report that credentials are missing, and vice versa. 113
114
## Architecturedev-redirects.sglmodified
@@ -9,4 +9,7 @@
9
use: (from-path dir: "../sigil-youtube")) 10
(for-repo 11
url: "codeberg:sigil/sigil-twitch"−12
use: (from-path dir: "../sigil-twitch"))))+12
use: (from-path dir: "../sigil-twitch"))+13
(for-repo+14
url: "codeberg:sigil/sigil-oauth"+15
use: (from-path dir: "../sigil-oauth"))))package.sglmodified
@@ -7,6 +7,7 @@
7
(define sigil-repo "codeberg:sigil/sigil") 8
(define youtube-repo "codeberg:sigil/sigil-youtube") 9
(define twitch-repo "codeberg:sigil/sigil-twitch")+10
(define oauth-repo "codeberg:sigil/sigil-oauth") 11
12
(package 13
name: "tube"@@ -38,7 +39,10 @@
39
mcp-server: #{ name: "tube" 40
args: #["serve"] 41
secret-env: #{ YOUTUBE_ACCESS_TOKEN: #t+42
YOUTUBE_CLIENT_ID: #t+43
YOUTUBE_CLIENT_SECRET: #t 44
TWITCH_CLIENT_ID: #t+45
TWITCH_CLIENT_SECRET: #t 46
TWITCH_ACCESS_TOKEN: #t } }) 47
48
dependencies: (list@@ -51,7 +55,8 @@
55
(from-git url: sigil-repo package: "sigil-log") 56
(from-git url: sigil-repo package: "sigil-args") 57
(from-git url: youtube-repo package: "sigil-youtube")−54
(from-git url: twitch-repo package: "sigil-twitch"))+58
(from-git url: twitch-repo package: "sigil-twitch")+59
(from-git url: oauth-repo package: "sigil-oauth")) 60
61
tasks: (list 62
(tasksrc/tube/config.sglmodified
@@ -1,9 +1,11 @@
1
;;; (tube config) - Configuration from environment variables. 2
;;; 3
;;; Reads YouTube and Twitch credentials from environment.−4
;;; YOUTUBE_ACCESS_TOKEN / YOUTUBE_API_KEY for YouTube,−5
;;; TWITCH_CLIENT_ID / TWITCH_ACCESS_TOKEN for Twitch,−6
;;; TUBE_DEFAULT_PLATFORM for default platform preference.+4
;;; Supports both direct access tokens and OAuth client credentials.+5
;;;+6
;;; Direct tokens: YOUTUBE_ACCESS_TOKEN, TWITCH_ACCESS_TOKEN+7
;;; OAuth flow: YOUTUBE_CLIENT_ID/SECRET, TWITCH_CLIENT_ID/SECRET+8
;;; (tokens stored in ~/.config/sigil/<service>/tokens.json) 9
10
(define-library (tube config) 11
(import (sigil core)@@ -14,7 +16,10 @@
16
tube-config? 17
tube-config-youtube-access-token 18
tube-config-youtube-api-key+19
tube-config-youtube-client-id+20
tube-config-youtube-client-secret 21
tube-config-twitch-client-id+22
tube-config-twitch-client-secret 23
tube-config-twitch-access-token 24
tube-config-default-platform 25
@@ -24,22 +29,35 @@
29
(define-struct tube-config 30
(youtube-access-token default: #f) 31
(youtube-api-key default: #f)+32
(youtube-client-id default: #f)+33
(youtube-client-secret default: #f) 34
(twitch-client-id default: #f)+35
(twitch-client-secret default: #f) 36
(twitch-access-token default: #f) 37
(default-platform default: "both")) 38
39
;;; Load configuration from environment variables. 40
;;;−33
;;; - YOUTUBE_ACCESS_TOKEN — OAuth2 token for YouTube (required for mutations)−34
;;; - YOUTUBE_API_KEY — API key for YouTube read-only operations−35
;;; - TWITCH_CLIENT_ID — Twitch application client ID−36
;;; - TWITCH_ACCESS_TOKEN — Twitch OAuth2 token−37
;;; - TUBE_DEFAULT_PLATFORM — default platform: "youtube", "twitch", or "both"+41
;;; Direct token mode:+42
;;; YOUTUBE_ACCESS_TOKEN — OAuth2 token for YouTube (required for mutations)+43
;;; YOUTUBE_API_KEY — API key for YouTube read-only operations+44
;;; TWITCH_ACCESS_TOKEN — Twitch OAuth2 token+45
;;;+46
;;; OAuth mode (tokens auto-managed via sigil-oauth):+47
;;; YOUTUBE_CLIENT_ID — Google Cloud OAuth2 client ID+48
;;; YOUTUBE_CLIENT_SECRET — Google Cloud OAuth2 client secret+49
;;; TWITCH_CLIENT_ID — Twitch application client ID+50
;;; TWITCH_CLIENT_SECRET — Twitch application client secret+51
;;;+52
;;; TUBE_DEFAULT_PLATFORM — default platform: "youtube", "twitch", or "both" 53
(define (load-tube-config) 54
(tube-config 55
youtube-access-token: (getenv "YOUTUBE_ACCESS_TOKEN") 56
youtube-api-key: (getenv "YOUTUBE_API_KEY")+57
youtube-client-id: (getenv "YOUTUBE_CLIENT_ID")+58
youtube-client-secret: (getenv "YOUTUBE_CLIENT_SECRET") 59
twitch-client-id: (getenv "TWITCH_CLIENT_ID")+60
twitch-client-secret: (getenv "TWITCH_CLIENT_SECRET") 61
twitch-access-token: (getenv "TWITCH_ACCESS_TOKEN") 62
default-platform: (getenv-default "TUBE_DEFAULT_PLATFORM" "both"))) 63
src/tube/main.sglmodified
@@ -10,6 +10,10 @@
10
(sigil process) 11
(sigil args) 12
(sigil mcp server)+13
(sigil time)+14
(sigil oauth server)+15
(youtube oauth)+16
(twitch oauth) 17
(tube config) 18
(tube youtube) 19
(tube twitch)@@ -110,6 +114,45 @@
114
(display (tool-status config params)) 115
(newline))) 116
+117
;; ============================================================+118
;; Auth Command+119
;; ============================================================+120
+121
(define (cli-auth-youtube config)+122
(let ((client-id (tube-config-youtube-client-id config))+123
(client-secret (tube-config-youtube-client-secret config)))+124
(unless (and client-id client-secret)+125
(display "Error: YOUTUBE_CLIENT_ID and YOUTUBE_CLIENT_SECRET must be set.\n")+126
(exit 1))+127
(let* ((oauth-cfg (youtube-oauth-config client-id client-secret))+128
(tokens (oauth-run-authorization-flow oauth-cfg+129
(current-second))))+130
(youtube-save-tokens tokens)+131
(display "YouTube authorization successful. Tokens saved.\n"))))+132
+133
(define (cli-auth-twitch config)+134
(let ((client-id (tube-config-twitch-client-id config))+135
(client-secret (tube-config-twitch-client-secret config)))+136
(unless (and client-id client-secret)+137
(display "Error: TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET must be set.\n")+138
(exit 1))+139
(let* ((oauth-cfg (twitch-oauth-config client-id client-secret))+140
(tokens (oauth-run-authorization-flow oauth-cfg+141
(current-second))))+142
(twitch-save-tokens tokens)+143
(display "Twitch authorization successful. Tokens saved.\n"))))+144
+145
(define (cli-auth config opts args)+146
(let ((platform (if (pair? args) (car args) #f)))+147
(cond+148
((equal? platform "youtube") (cli-auth-youtube config))+149
((equal? platform "twitch") (cli-auth-twitch config))+150
(else+151
(display "Usage: tube auth <youtube|twitch>\n\n")+152
(display "Run the OAuth authorization flow for a platform.\n")+153
(display "Requires CLIENT_ID and CLIENT_SECRET env vars.\n")+154
(display "Tokens are stored in ~/.config/sigil/<service>/tokens.json\n")))))+155
156
;; ============================================================ 157
;; CLI Definition 158
;; ============================================================@@ -191,7 +234,12 @@
234
name: "status" 235
description: "Platform overview" 236
options: (list (platform-option default-plat))−194
handler: (lambda (opts args) (cli-status config opts)))))))+237
handler: (lambda (opts args) (cli-status config opts)))+238
+239
(command+240
name: "auth"+241
description: "Authorize a platform via OAuth"+242
handler: (lambda (opts args) (cli-auth config opts args))))))) 243
244
;; ============================================================ 245
;; Entry Pointsrc/tube/twitch.sglmodified
@@ -3,6 +3,10 @@
3
;;; Channel info, stream scheduling, analytics (followers, subs, clips), 4
;;; and chat operations. All operations that need the broadcaster ID 5
;;; resolve it once via with-twitch-context to avoid repeated API calls.+6
;;;+7
;;; Supports two authentication modes:+8
;;; 1. Direct access token via TWITCH_CLIENT_ID + TWITCH_ACCESS_TOKEN env vars+9
;;; 2. OAuth via TWITCH_CLIENT_ID + TWITCH_CLIENT_SECRET with auto-managed tokens 10
11
(define-library (tube twitch) 12
(import (sigil core)@@ -10,10 +14,12 @@
14
(sigil dict) 15
(sigil struct) 16
(sigil json)+17
(sigil time) 18
(twitch) 19
(twitch schedule) 20
(twitch analytics) 21
(twitch chat)+22
(twitch oauth) 23
(tube config)) 24
(export require-twitch-client 25
with-twitch-context@@ -32,14 +38,40 @@
38
format-twitch-video) 39
(begin 40
−35
;;; Build a twitch-client from config, raising if no credentials.+41
;;; Build a twitch-client from config.+42
;;;+43
;;; Priority:+44
;;; 1. If TWITCH_ACCESS_TOKEN is set, use it with TWITCH_CLIENT_ID (legacy)+45
;;; 2. If TWITCH_CLIENT_ID + TWITCH_CLIENT_SECRET are set, use OAuth+46
;;; with stored tokens (auto-refreshes expired tokens)+47
;;; 3. Otherwise, raise an error 48
(define (require-twitch-client config) 49
(let ((client-id (tube-config-twitch-client-id config))−38
(token (tube-config-twitch-access-token config)))−39
(unless (and client-id token)−40
(error "Twitch credentials not set. Set TWITCH_CLIENT_ID and TWITCH_ACCESS_TOKEN."))−41
(twitch-client client-id: client-id−42
access-token: token)))+50
(token (tube-config-twitch-access-token config))+51
(client-secret (tube-config-twitch-client-secret config)))+52
(cond+53
;; Direct token mode — needs both client-id and token+54
((and client-id token)+55
(twitch-client client-id: client-id+56
access-token: token))+57
;; OAuth mode — client-id + secret, load stored tokens+58
((and client-id client-secret)+59
(let* ((oauth-cfg (twitch-oauth-config client-id client-secret))+60
(tokens (twitch-load-tokens)))+61
(if (not tokens)+62
(error (string-append+63
"No stored Twitch tokens found. "+64
"Run 'tube auth twitch' to authorize, "+65
"or set TWITCH_ACCESS_TOKEN directly.")))+66
(let-values (((client fresh-tokens)+67
(twitch-ensure-client oauth-cfg tokens+68
(current-second))))+69
client)))+70
(else+71
(error (string-append+72
"Twitch credentials not set. "+73
"Set TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET for OAuth, "+74
"or TWITCH_CLIENT_ID and TWITCH_ACCESS_TOKEN for direct mode.")))))) 75
76
;;; Resolve client + broadcaster ID together. Caches the broadcaster ID 77
;;; lookup so multiple operations in the same tool handler share one call.src/tube/youtube.sglmodified
@@ -2,6 +2,10 @@
2
;;; 3
;;; Upload with metadata, video listing with stats, analytics dashboard, 4
;;; livestream management, and playlist management.+5
;;;+6
;;; Supports two authentication modes:+7
;;; 1. Direct access token via YOUTUBE_ACCESS_TOKEN env var+8
;;; 2. OAuth via YOUTUBE_CLIENT_ID/SECRET with auto-managed token storage 9
10
(define-library (tube youtube) 11
(import (sigil core)@@ -9,11 +13,13 @@
13
(sigil dict) 14
(sigil struct) 15
(sigil json)+16
(sigil time) 17
(youtube) 18
(youtube upload) 19
(youtube analytics) 20
(youtube live) 21
(youtube playlist)+22
(youtube oauth) 23
(tube config)) 24
(export require-youtube-client 25
tube-upload-video@@ -33,14 +39,45 @@
39
youtube-video-title) 40
(begin 41
−36
;;; Build a youtube-client from config, raising if no credentials.+42
;;; Build a youtube-client from config.+43
;;;+44
;;; Priority:+45
;;; 1. If YOUTUBE_ACCESS_TOKEN is set, use it directly (legacy mode)+46
;;; 2. If YOUTUBE_CLIENT_ID/SECRET are set, use OAuth with stored tokens+47
;;; (auto-refreshes expired tokens)+48
;;; 3. If YOUTUBE_API_KEY is set, use it for read-only access+49
;;; 4. Otherwise, raise an error 50
(define (require-youtube-client config) 51
(let ((token (tube-config-youtube-access-token config))−39
(key (tube-config-youtube-api-key config)))−40
(unless (or token key)−41
(error "No YouTube credentials set. Set YOUTUBE_ACCESS_TOKEN or YOUTUBE_API_KEY."))−42
(youtube-client access-token: token−43
api-key: key)))+52
(key (tube-config-youtube-api-key config))+53
(client-id (tube-config-youtube-client-id config))+54
(client-secret (tube-config-youtube-client-secret config)))+55
(cond+56
;; Direct token — use as-is+57
(token+58
(youtube-client access-token: token+59
api-key: key))+60
;; OAuth client credentials — load/refresh stored tokens+61
((and client-id client-secret)+62
(let* ((oauth-cfg (youtube-oauth-config client-id client-secret))+63
(tokens (youtube-load-tokens)))+64
(if (not tokens)+65
(error (string-append+66
"No stored YouTube tokens found. "+67
"Run 'tube auth youtube' to authorize, "+68
"or set YOUTUBE_ACCESS_TOKEN directly.")))+69
(let-values (((client fresh-tokens)+70
(youtube-ensure-client oauth-cfg tokens+71
(current-second))))+72
client)))+73
;; API key only — read-only+74
(key+75
(youtube-client api-key: key))+76
(else+77
(error (string-append+78
"No YouTube credentials set. "+79
"Set YOUTUBE_CLIENT_ID and YOUTUBE_CLIENT_SECRET for OAuth, "+80
"or YOUTUBE_ACCESS_TOKEN for direct token mode.")))))) 81
82
;; ============================================================ 83
;; Upload