Commitc2659202Recorded25 Mar 2026Repositorytube
Fix 6 bugs found during code review
Message
- Export tool handler functions from (tube tools) so CLI commands work
- Re-export youtube-video-id/title from (tube youtube) for cli-upload
- Read file contents in MCP tool-upload handler (was passing path string)
- Wire up days parameter in analytics to actually compute date range
- Handle JSON null in format-schedule-segment (null symbol is truthy)
- Pre-compute date for tool-go-live to avoid VM yield bug in async
Changed
src/tube/main.sgl | 23 ++++++++++++++---------
src/tube/tools.sgl | 51 ++++++++++++++++++++++++++++++++++++---------------
src/tube/twitch.sgl | 13 +++++++++----
src/tube/youtube.sgl | 5 ++++-
4 files changed, 63 insertions(+), 29 deletions(-)Diff
src/tube/main.sglmodified
@@ -6,6 +6,7 @@
6
(define-library (tube main) 7
(import (sigil core) 8
(sigil string)+9
(sigil dict) 10
(sigil process) 11
(sigil mcp server) 12
(tube config)@@ -101,16 +102,19 @@
102
(display "\n\n")) 103
videos))))) 104
−104
(define (cli-analytics config args start-default end-default)−105
(let* ((days (find-flag args "--days" "30"))+105
(define (cli-analytics config args start-default end-default+106
now-timestamp)+107
(let* ((days (find-flag args "--days" #f)) 108
(platform (find-flag args "--platform" 109
(tube-config-default-platform config)))−108
(params #{ days: (string->number days)−109
platform: platform−110
start_date: start-default−111
end_date: end-default }))+110
(params #{ platform: platform+111
end_date: end-default })+112
(params (if days+113
(dict-set params days: (string->number days))+114
params))) 115
;; Reuse the tool handler−113
(display (tool-analytics config params start-default end-default))+116
(display (tool-analytics config params start-default end-default+117
now-timestamp)) 118
(newline))) 119
120
(define (cli-schedule config args)@@ -156,7 +160,8 @@
160
(let ((config (load-tube-config)) 161
(args (cdr (command-line))) 162
(start-default (default-start-date))−159
(end-default (default-end-date)))+163
(end-default (default-end-date))+164
(now-ts (default-now-timestamp))) 165
(cond 166
((null? args) 167
(print-usage)@@ -168,7 +173,7 @@
173
((string=? (car args) "videos") 174
(cli-videos config (cdr args))) 175
((string=? (car args) "analytics")−171
(cli-analytics config (cdr args) start-default end-default))+176
(cli-analytics config (cdr args) start-default end-default now-ts)) 177
((string=? (car args) "stream-schedule") 178
(cli-schedule config (cdr args))) 179
((string=? (car args) "go-live")src/tube/tools.sglmodified
@@ -23,7 +23,13 @@
23
(tube twitch)) 24
(export register-tube-tools! 25
default-start-date−26
default-end-date)+26
default-end-date+27
default-now-timestamp+28
tool-analytics+29
tool-schedule+30
tool-go-live+31
tool-chat+32
tool-status) 33
(begin 34
35
;;; Compute default start date (30 days ago).@@ -38,6 +44,11 @@
44
(define (default-end-date) 45
(format-date (current-second))) 46
+47
;;; Snapshot the current timestamp for days-based date computation.+48
;;; Must be called outside async contexts.+49
(define (default-now-timestamp)+50
(current-second))+51
52
;;; Check if a platform is enabled based on config and requested platform. 53
(define (platform-enabled? config platform requested) 54
(let ((req (or requested (tube-config-default-platform config))))@@ -73,11 +84,14 @@
84
(privacy (dict-ref params privacy: "private"))) 85
(unless file 86
(error "file parameter is required"))−76
(let ((result (tube-upload-video config file title description privacy)))−77
(string-append "Video uploaded successfully.\n"−78
"Video ID: " (youtube-video-id result) "\n"−79
"Title: " (youtube-video-title result) "\n"−80
"Privacy: " privacy))))+87
(let* ((port (open-binary-input-file file))+88
(data (read-bytevector-all port)))+89
(close-input-port port)+90
(let ((result (tube-upload-video config data title description privacy)))+91
(string-append "Video uploaded successfully.\n"+92
"Video ID: " (youtube-video-id result) "\n"+93
"Title: " (youtube-video-title result) "\n"+94
"Privacy: " privacy))))) 95
96
(define (tool-videos config params) 97
(let* ((limit (dict-ref params limit: 10))@@ -88,10 +102,16 @@
102
"Recent videos (" (number->string (length videos)) "):\n\n" 103
(string-join (map format-video videos) "\n\n"))))) 104
−91
(define (tool-analytics config params start-default end-default)+105
(define (tool-analytics config params start-default end-default+106
now-timestamp) 107
(let* ((platform (dict-ref params platform: 108
(tube-config-default-platform config)))−94
(start-date (dict-ref params start_date: start-default))+109
(days (dict-ref params days: #f))+110
(start-date (dict-ref params start_date:+111
(if days+112
(format-date (- now-timestamp+113
(* days 86400)))+114
start-default))) 115
(end-date (dict-ref params end_date: end-default))) 116
(collect-platform-results config platform 117
(list@@ -143,13 +163,13 @@
163
(string-join (map format-schedule-segment segments) "\n"))))))) 164
"No schedule data available. Check platform credentials."))) 165
−146
(define (tool-go-live config params)+166
(define (tool-go-live config params . rest) 167
(let* ((platform (dict-ref params platform: 168
(tube-config-default-platform config))) 169
(title (dict-ref params title: "Live Stream"))−150
;; Pre-compute date outside platform lambdas to avoid−151
;; calling format-date inside async context−152
(today (default-end-date)))+170
;; Use pre-computed date to avoid calling current-second+171
;; inside async context (VM yield bug)+172
(today (if (pair? rest) (car rest) (default-end-date)))) 173
(collect-platform-results config platform 174
(list 175
(cons "youtube"@@ -201,7 +221,8 @@
221
;;; the VM yield bug with current-second in async goroutines. 222
(define (register-tube-tools! server config) 223
(let ((start-default (default-start-date))−204
(end-default (default-end-date)))+224
(end-default (default-end-date))+225
(now-ts (default-now-timestamp))) 226
227
(mcp-server-register-tool! server 228
name: "tube/upload"@@ -243,7 +264,7 @@
264
description: "Start date in YYYY-MM-DD format" } 265
end_date: #{ type: "string" 266
description: "End date in YYYY-MM-DD format" } } }−246
handler: (lambda (params) (tool-analytics config params start-default end-default)))+267
handler: (lambda (params) (tool-analytics config params start-default end-default now-ts))) 268
269
(mcp-server-register-tool! server 270
name: "tube/schedule"@@ -265,7 +286,7 @@
286
platform: #{ type: "string" 287
description: "Platform: youtube, twitch, or both (default: both)" 288
enum: #["youtube" "twitch" "both"] } } }−268
handler: (lambda (params) (tool-go-live config params)))+289
handler: (lambda (params) (tool-go-live config params end-default))) 290
291
(mcp-server-register-tool! server 292
name: "tube/chat"src/tube/twitch.sglmodified
@@ -93,16 +93,21 @@
93
" viewers)") 94
"Offline")))))) 95
+96
;;; Check if a value is absent (either #f or JSON null symbol).+97
(define (absent? v)+98
(or (not v) (eq? v 'null)))+99
100
;;; Format a schedule segment. 101
(define (format-schedule-segment seg) 102
(string-append 103
(twitch-schedule-segment-start-time seg) " - " 104
(twitch-schedule-segment-end-time seg) " "−101
(or (twitch-schedule-segment-title seg) "(no title)")+105
(let ((title (twitch-schedule-segment-title seg)))+106
(if (absent? title) "(no title)" title)) 107
(let ((cat (twitch-schedule-segment-category seg)))−103
(if cat−104
(string-append " [" (dict-ref cat name: "unknown") "]")−105
""))+108
(if (absent? cat)+109
""+110
(string-append " [" (dict-ref cat name: "unknown") "]"))) 111
(if (twitch-schedule-segment-is-recurring seg) 112
" (recurring)" 113
"")))src/tube/youtube.sglmodified
@@ -27,7 +27,10 @@
27
format-video 28
format-analytics-row 29
format-broadcast−30
format-playlist)+30
format-playlist+31
;; Re-exported from (sigil youtube) for CLI use+32
youtube-video-id+33
youtube-video-title) 34
(begin 35
36
;;; Build a youtube-client from config, raising if no credentials.