@@ -5,7 +5,10 @@ import (
55 "encoding/json"
66 "errors"
77 "fmt"
8+ "io"
9+ "net/http"
810 "os"
11+ "os/exec"
912 "os/signal"
1013 "path/filepath"
1114 "strings"
@@ -84,6 +87,15 @@ func main() {
8487 os .Exit (2 )
8588 }
8689 return
90+ case "version" :
91+ runVersion ()
92+ return
93+ case "update" :
94+ if err := runUpdate (ctx ); err != nil {
95+ fmt .Fprintln (os .Stderr , "kairo update:" , err )
96+ os .Exit (2 )
97+ }
98+ return
8799 }
88100 }
89101
@@ -231,3 +243,146 @@ func runImport(ctx context.Context, repo *storage.Repository, args []string) err
231243 }
232244 return nil
233245}
246+
247+ func runVersion () {
248+ version := getCurrentVersion ()
249+ fmt .Printf ("kairo version %s\n " , version )
250+ }
251+
252+ func getCurrentVersion () string {
253+ // Try current working directory first (most common for development/running)
254+ if data , err := os .ReadFile ("VERSION.txt" ); err == nil {
255+ return strings .TrimSpace (string (data ))
256+ }
257+
258+ // Try repository root relative to binary location
259+ if ex , err := os .Executable (); err == nil {
260+ versionPath := filepath .Join (filepath .Dir (ex ), "VERSION.txt" )
261+ if data , err := os .ReadFile (versionPath ); err == nil {
262+ return strings .TrimSpace (string (data ))
263+ }
264+
265+ // Try going up from the binary directory (for development builds)
266+ versionPath = filepath .Join (filepath .Dir (ex ), ".." , "VERSION.txt" )
267+ if data , err := os .ReadFile (versionPath ); err == nil {
268+ return strings .TrimSpace (string (data ))
269+ }
270+ }
271+
272+ // If not found in expected locations, try home config directory
273+ homeDir , _ := os .UserHomeDir ()
274+ altPath := filepath .Join (homeDir , ".config" , "kairo" , "VERSION.txt" )
275+ if data , err := os .ReadFile (altPath ); err == nil {
276+ return strings .TrimSpace (string (data ))
277+ }
278+
279+ // Fallback to default
280+ return "1.0.0"
281+ }
282+
283+ type GitHubRelease struct {
284+ TagName string `json:"tag_name"`
285+ Draft bool `json:"draft"`
286+ Prerelease bool `json:"prerelease"`
287+ }
288+
289+ func getLatestGitHubRelease (ctx context.Context ) (string , error ) {
290+ // Query GitHub API for the latest release
291+ url := "https://api.github.com/repos/programmersd21/kairo/releases/latest"
292+ req , err := http .NewRequestWithContext (ctx , "GET" , url , nil )
293+ if err != nil {
294+ return "" , err
295+ }
296+
297+ req .Header .Set ("Accept" , "application/vnd.github.v3+json" )
298+
299+ client := & http.Client {}
300+ resp , err := client .Do (req )
301+ if err != nil {
302+ return "" , err
303+ }
304+ defer func () {
305+ _ = resp .Body .Close ()
306+ }()
307+
308+ if resp .StatusCode != http .StatusOK {
309+ return "" , fmt .Errorf ("GitHub API returned status %d" , resp .StatusCode )
310+ }
311+
312+ body , err := io .ReadAll (resp .Body )
313+ if err != nil {
314+ return "" , err
315+ }
316+
317+ var release GitHubRelease
318+ if err := json .Unmarshal (body , & release ); err != nil {
319+ return "" , err
320+ }
321+
322+ // Remove 'v' prefix if present
323+ version := strings .TrimPrefix (release .TagName , "v" )
324+ return version , nil
325+ }
326+
327+ func compareVersions (current , latest string ) int {
328+ // Parse semantic versions: x.y.z
329+ currentParts := strings .Split (strings .TrimPrefix (current , "v" ), "." )
330+ latestParts := strings .Split (strings .TrimPrefix (latest , "v" ), "." )
331+
332+ // Pad with zeros if needed
333+ for len (currentParts ) < 3 {
334+ currentParts = append (currentParts , "0" )
335+ }
336+ for len (latestParts ) < 3 {
337+ latestParts = append (latestParts , "0" )
338+ }
339+
340+ // Simple numeric comparison
341+ for i := 0 ; i < 3 ; i ++ {
342+ var curr , last int
343+ _ , _ = fmt .Sscanf (currentParts [i ], "%d" , & curr )
344+ _ , _ = fmt .Sscanf (latestParts [i ], "%d" , & last )
345+
346+ if last > curr {
347+ return - 1 // latest is newer
348+ }
349+ if last < curr {
350+ return 1 // current is newer
351+ }
352+ }
353+ return 0 // equal
354+ }
355+
356+ func runUpdate (ctx context.Context ) error {
357+ currentVersion := getCurrentVersion ()
358+ fmt .Printf ("Checking for updates (current: %s)...\n " , currentVersion )
359+
360+ latestVersion , err := getLatestGitHubRelease (ctx )
361+ if err != nil {
362+ return fmt .Errorf ("failed to fetch latest release: %w" , err )
363+ }
364+
365+ cmp := compareVersions (currentVersion , latestVersion )
366+ if cmp >= 0 {
367+ // Already on latest or newer version
368+ fmt .Printf ("✓ Updated to latest version %s\n " , currentVersion )
369+ return nil
370+ }
371+
372+ // There's a newer version available
373+ fmt .Printf ("Found newer version: %s\n " , latestVersion )
374+ fmt .Println ("Updating kairo..." )
375+
376+ // Execute: go install github.com/programmersd21/kairo/cmd/kairo@latest
377+ cmd := exec .CommandContext (ctx , "go" , "install" , "github.com/programmersd21/kairo/cmd/kairo@latest" )
378+ cmd .Stdout = os .Stdout
379+ cmd .Stderr = os .Stderr
380+ cmd .Stdin = os .Stdin
381+
382+ if err := cmd .Run (); err != nil {
383+ return fmt .Errorf ("failed to update: %w" , err )
384+ }
385+
386+ fmt .Printf ("✓ Kairo updated to version %s successfully!\n " , latestVersion )
387+ return nil
388+ }
0 commit comments