@@ -328,34 +328,47 @@ func (m model) Init() tea.Cmd {
328328
329329// ── Commands ────────────────────────────────────────────────────────────────
330330
331- func fetchPackages () tea. Msg {
331+ func loadPackages () ([] Package , bool , string , time. Time , error ) {
332332 if path := cacheFilePath (); path != "" {
333333 if packages , fetchedAt := loadCache (path ); packages != nil {
334- return packagesFetchedMsg { packages : packages , fromCache : true , cachePath : path , cacheFetchedAt : fetchedAt }
334+ return packages , true , path , fetchedAt , nil
335335 }
336336 }
337337
338338 resp , err := http .Get (apiURL )
339339 if err != nil {
340- return fetchErrMsg { err }
340+ return nil , false , "" , time. Time {}, err
341341 }
342342 defer resp .Body .Close ()
343343
344344 body , err := io .ReadAll (resp .Body )
345345 if err != nil {
346- return fetchErrMsg { err }
346+ return nil , false , "" , time. Time {}, err
347347 }
348348
349349 var packages []Package
350350 if err := json .Unmarshal (body , & packages ); err != nil {
351- return fetchErrMsg { err }
351+ return nil , false , "" , time. Time {}, err
352352 }
353353
354354 if path := cacheFilePath (); path != "" {
355355 writeCache (path , packages )
356356 }
357357
358- return packagesFetchedMsg {packages : packages }
358+ return packages , false , "" , time.Time {}, nil
359+ }
360+
361+ func fetchPackages () tea.Msg {
362+ packages , fromCache , cachePath , fetchedAt , err := loadPackages ()
363+ if err != nil {
364+ return fetchErrMsg {err }
365+ }
366+ return packagesFetchedMsg {
367+ packages : packages ,
368+ fromCache : fromCache ,
369+ cachePath : cachePath ,
370+ cacheFetchedAt : fetchedAt ,
371+ }
359372}
360373
361374// ── Filtering ───────────────────────────────────────────────────────────────
@@ -846,6 +859,91 @@ func renderName(name string, matchedIndexes []int, selected bool) string {
846859 return result .String ()
847860}
848861
862+ // ── Static (non-interactive) mode ───────────────────────────────────────────
863+
864+ func findPackage (packages []Package , query string ) * Package {
865+ for i := range packages {
866+ if strings .EqualFold (packages [i ].Name , query ) {
867+ return & packages [i ]
868+ }
869+ }
870+ return nil
871+ }
872+
873+ func renderStaticDetail (pkg * Package ) string {
874+ var b strings.Builder
875+
876+ header := headerStyle .Render (pkg .Name )
877+ if len (pkg .Categories ) > 0 {
878+ cats := categoryStyle .Render (" " + strings .Join (pkg .Categories , " · " ))
879+ b .WriteString (lipgloss .JoinHorizontal (lipgloss .Center , header , cats ) + "\n " )
880+ } else {
881+ b .WriteString (header + "\n " )
882+ }
883+
884+ relHeader := subtitleStyle .Bold (true ).MarginTop (1 ).Render (" Releases" )
885+ b .WriteString (relHeader + "\n " )
886+ b .WriteString (" " + dividerStyle .Render (strings .Repeat ("─" , 52 )) + "\n " )
887+
888+ releases := make ([]Release , 0 , len (pkg .Releases )+ 1 )
889+ releases = append (releases , pkg .LatestStable )
890+ for _ , r := range pkg .Releases {
891+ if r .Version != pkg .LatestStable .Version {
892+ releases = append (releases , r )
893+ }
894+ }
895+
896+ for i , rel := range releases {
897+ isLatest := i == 0
898+ v := relVerDimStyle .Render (fmt .Sprintf ("%-28s" , rel .Version ))
899+ d := dateStyle .Render (rel .Date )
900+ line := fmt .Sprintf (" %s %s" , v , d )
901+ if rel .Prerelease {
902+ line += " " + prerelDimStyle .Render ("pre" )
903+ }
904+ if isLatest {
905+ line += " " + latestDimBadge .Render ("latest" )
906+ }
907+ b .WriteString (line + "\n " )
908+
909+ if isLatest {
910+ b .WriteString (" " + dividerStyle .Render (strings .Repeat ("─" , 52 )) + "\n " )
911+ }
912+ }
913+
914+ return b .String ()
915+ }
916+
917+ func runStatic (query string ) int {
918+ packages , _ , _ , _ , err := loadPackages ()
919+ if err != nil {
920+ title := errorTitleStyle .Render ("Failed to fetch package data" )
921+ detail := errorDetailStyle .Render (err .Error ())
922+ box := errorBoxStyle .Render (title + "\n " + detail )
923+ fmt .Fprintln (os .Stderr , "\n " + box + "\n " )
924+ return 1
925+ }
926+
927+ pkg := findPackage (packages , query )
928+ if pkg == nil {
929+ fmt .Fprintf (os .Stderr , "Package %q not found." , query )
930+ matches := fuzzy .FindFrom (query , packageSource (packages ))
931+ if len (matches ) > 0 {
932+ limit := min (len (matches ), 5 )
933+ suggestions := make ([]string , 0 , limit )
934+ for _ , m := range matches [:limit ] {
935+ suggestions = append (suggestions , packages [m .Index ].Name )
936+ }
937+ fmt .Fprintf (os .Stderr , " Did you mean: %s?" , strings .Join (suggestions , ", " ))
938+ }
939+ fmt .Fprintln (os .Stderr )
940+ return 1
941+ }
942+
943+ fmt .Println (renderStaticDetail (pkg ))
944+ return 0
945+ }
946+
849947// ── Main ────────────────────────────────────────────────────────────────────
850948
851949func main () {
@@ -857,6 +955,10 @@ func main() {
857955 return
858956 }
859957
958+ if args := flag .Args (); len (args ) > 0 {
959+ os .Exit (runStatic (strings .Join (args , " " )))
960+ }
961+
860962 p := tea .NewProgram (initialModel (), tea .WithAltScreen ())
861963 finalModel , err := p .Run ()
862964 if err != nil {
0 commit comments