66 "encoding/json"
77 "fmt"
88 "net/http"
9+ "os"
10+ "path/filepath"
911 "strings"
1012 "time"
1113)
@@ -23,10 +25,19 @@ type LatestRelease struct {
2325}
2426
2527const (
26- releaseRepo = "bborn/taskyou"
27- releaseTimeout = 5 * time .Second
28+ releaseRepo = "bborn/taskyou"
29+ releaseTimeout = 5 * time .Second
30+ versionCacheTTL = 24 * time .Hour
31+ cacheFileName = "version-check.json"
2832)
2933
34+ // versionCache is the on-disk cache for the latest release check.
35+ type versionCache struct {
36+ Version string `json:"version"`
37+ URL string `json:"url"`
38+ CheckedAt time.Time `json:"checked_at"`
39+ }
40+
3041// FetchLatestRelease queries the GitHub API for the latest release.
3142// Returns nil if the request fails or no release exists.
3243func FetchLatestRelease () * LatestRelease {
@@ -114,3 +125,70 @@ func parseVersion(v string) []int {
114125 }
115126 return result
116127}
128+
129+ // cacheDir returns the directory used for caching version check results.
130+ // Defaults to ~/.local/share/task/ but respects WORKTREE_DB_PATH if set.
131+ func cacheDir () string {
132+ if p := os .Getenv ("WORKTREE_DB_PATH" ); p != "" {
133+ return filepath .Dir (p )
134+ }
135+ home , _ := os .UserHomeDir ()
136+ return filepath .Join (home , ".local" , "share" , "task" )
137+ }
138+
139+ // readCache loads the version check cache from disk. Returns nil if missing or unreadable.
140+ func readCache () * versionCache {
141+ data , err := os .ReadFile (filepath .Join (cacheDir (), cacheFileName ))
142+ if err != nil {
143+ return nil
144+ }
145+ var c versionCache
146+ if err := json .Unmarshal (data , & c ); err != nil {
147+ return nil
148+ }
149+ return & c
150+ }
151+
152+ // writeCache persists the version check result to disk.
153+ func writeCache (release * LatestRelease ) {
154+ c := versionCache {
155+ Version : release .Version ,
156+ URL : release .URL ,
157+ CheckedAt : time .Now (),
158+ }
159+ data , err := json .Marshal (c )
160+ if err != nil {
161+ return
162+ }
163+ dir := cacheDir ()
164+ _ = os .MkdirAll (dir , 0o755 )
165+ _ = os .WriteFile (filepath .Join (dir , cacheFileName ), data , 0o644 )
166+ }
167+
168+ // CLIVersionCheck checks if a newer version is available, using a 24h on-disk cache.
169+ // Returns the latest release if an upgrade is available, nil otherwise.
170+ // Skips the check entirely for "dev" builds.
171+ func CLIVersionCheck (currentVersion string ) * LatestRelease {
172+ if currentVersion == "" || currentVersion == "dev" {
173+ return nil
174+ }
175+
176+ // Try the cache first
177+ if c := readCache (); c != nil && time .Since (c .CheckedAt ) < versionCacheTTL {
178+ release := & LatestRelease {Version : c .Version , URL : c .URL }
179+ if IsNewerVersion (currentVersion , release .Version ) {
180+ return release
181+ }
182+ return nil
183+ }
184+
185+ // Cache miss or stale — fetch from GitHub
186+ release := FetchLatestRelease ()
187+ if release != nil {
188+ writeCache (release )
189+ if IsNewerVersion (currentVersion , release .Version ) {
190+ return release
191+ }
192+ }
193+ return nil
194+ }
0 commit comments