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.