77 "text/tabwriter"
88
99 "github.com/spf13/cobra"
10+ "github.com/yeasy/ask/internal/cache"
1011 "github.com/yeasy/ask/internal/config"
1112 "github.com/yeasy/ask/internal/github"
1213 "github.com/yeasy/ask/internal/repository"
@@ -17,28 +18,31 @@ import (
1718var searchCmd = & cobra.Command {
1819 Use : "search [keyword]" ,
1920 Short : "Search for skills on GitHub" ,
20- Long : `Search GitHub repositories with the 'agent-skill' topic.
21- You can provide an optional keyword to filter results (e.g. 'browser', 'python').` ,
22- Example : ` # Search for browser-related skills
23- ask skill search browser
21+ Long : `Search for skills matching the keyword.
22+
23+ By default, uses local cache if available (fastest), otherwise fetches from remote.
24+ Use --local to force local-only search.
25+ Use --remote to force remote API search.` ,
26+ Example : ` # Search (local-first, then remote)
27+ ask skill search mcp
2428
25- # Search for Python skills
26- ask skill search python
29+ # Force local cache only (offline, fastest)
30+ ask skill search mcp --local
2731
28- # Search all available skills
29- ask skill search` ,
32+ # Force remote API (latest data)
33+ ask skill search mcp --remote ` ,
3034 Run : func (cmd * cobra.Command , args []string ) {
3135 keyword := ""
3236 if len (args ) > 0 {
3337 keyword = strings .Join (args , " " )
3438 }
3539
36- fmt .Printf ("Searching for skills matching '%s'...\n " , keyword )
40+ forceLocal , _ := cmd .Flags ().GetBool ("local" )
41+ forceRemote , _ := cmd .Flags ().GetBool ("remote" )
3742
3843 // Load config or use default
3944 cfg , err := config .LoadConfig ()
4045 if err != nil {
41- // It's okay if config doesn't exist, use default
4246 def := config .DefaultConfig ()
4347 cfg = & def
4448 }
@@ -52,6 +56,45 @@ You can provide an optional keyword to filter results (e.g. 'browser', 'python')
5256 installedSkills [s .Name ] = true
5357 }
5458
59+ var allRepos []github.Repository
60+ var errors []string
61+ var searchSource string
62+
63+ // Check local cache first (unless --remote is specified)
64+ if ! forceRemote {
65+ reposCache , err := cache .NewReposCache ()
66+ if err == nil {
67+ cachedRepos := reposCache .GetCachedRepos ()
68+ if len (cachedRepos ) > 0 || forceLocal {
69+ fmt .Printf ("Searching local cache for '%s'...\n " , keyword )
70+ skills , _ := reposCache .SearchSkills (keyword )
71+
72+ for _ , skill := range skills {
73+ allRepos = append (allRepos , github.Repository {
74+ Name : skill .Name ,
75+ Description : skill .Description ,
76+ Source : "local:" + skill .RepoName ,
77+ })
78+ }
79+ searchSource = "local"
80+
81+ if len (allRepos ) > 0 || forceLocal {
82+ // Display results from local cache
83+ displaySearchResults (allRepos , installedSkills , searchSource )
84+ if forceLocal && len (allRepos ) == 0 {
85+ fmt .Println ("\n Tip: Run 'ask repo sync' to populate local cache." )
86+ }
87+ return
88+ }
89+ // No local results and not forced local, fall through to remote
90+ }
91+ }
92+ }
93+
94+ // Remote search
95+ fmt .Printf ("Searching for skills matching '%s'...\n " , keyword )
96+ searchSource = "remote"
97+
5598 // Create progress bar for scanning sources
5699 bar := ui .NewProgressBar (len (cfg .Repos ), "Scanning sources" )
57100
@@ -104,8 +147,6 @@ You can provide an optional keyword to filter results (e.g. 'browser', 'python')
104147 }(repo )
105148 }
106149
107- var allRepos []github.Repository
108- var errors []string
109150 for i := 0 ; i < len (cfg .Repos ); i ++ {
110151 result := <- results
111152 _ = bar .Add (1 )
@@ -119,41 +160,50 @@ You can provide an optional keyword to filter results (e.g. 'browser', 'python')
119160 fmt .Println ()
120161 if len (errors ) > 0 {
121162 fmt .Println ("Warning: Some sources failed to load:" )
122- for _ , err := range errors {
123- fmt .Printf (" - %s\n " , err )
163+ for _ , errMsg := range errors {
164+ fmt .Printf (" - %s\n " , errMsg )
124165 }
125166 fmt .Println ()
126167 }
127168
128- w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
129- _ , _ = fmt .Fprintln (w , "NAME\t SOURCE\t INSTALLED\t STARS\t DESCRIPTION" )
130- for _ , repo := range allRepos {
131- // Truncate description if too long
132- desc := repo .Description
133- if len (desc ) > 40 {
134- desc = desc [:37 ] + "..."
135- }
169+ displaySearchResults (allRepos , installedSkills , searchSource )
170+ },
171+ }
136172
137- // Check if installed
138- installed := ""
139- if installedSkills [repo .Name ] {
140- installed = "✓"
141- }
173+ func displaySearchResults (repos []github.Repository , installedSkills map [string ]bool , source string ) {
174+ w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
175+ _ , _ = fmt .Fprintln (w , "NAME\t SOURCE\t INSTALLED\t STARS\t DESCRIPTION" )
176+ for _ , repo := range repos {
177+ // Truncate description if too long
178+ desc := repo .Description
179+ if len (desc ) > 40 {
180+ desc = desc [:37 ] + "..."
181+ }
142182
143- // Format stars (use "-" for dir-based where stars are parent repo)
144- stars := fmt . Sprintf ( "%d" , repo . StargazersCount )
145- if repo .StargazersCount == 0 {
146- stars = "- "
147- }
183+ // Check if installed
184+ installed := ""
185+ if installedSkills [ repo .Name ] {
186+ installed = "✓ "
187+ }
148188
149- _ , _ = fmt .Fprintf (w , "%s\t %s\t %s\t %s\t %s\n " , repo .Name , repo .Source , installed , stars , desc )
189+ // Format stars (use "-" for local or dir-based)
190+ stars := fmt .Sprintf ("%d" , repo .StargazersCount )
191+ if repo .StargazersCount == 0 {
192+ stars = "-"
150193 }
151- _ = w .Flush ()
152194
153- fmt .Printf ("\n Found %d skills.\n " , len (allRepos ))
154- },
195+ _ , _ = fmt .Fprintf (w , "%s\t %s\t %s\t %s\t %s\n " , repo .Name , repo .Source , installed , stars , desc )
196+ }
197+ _ = w .Flush ()
198+
199+ fmt .Printf ("\n Found %d skills.\n " , len (repos ))
200+ if source == "local" {
201+ fmt .Println ("(from local cache - run 'ask repo sync' to update)" )
202+ }
155203}
156204
157205func init () {
158206 skillCmd .AddCommand (searchCmd )
207+ searchCmd .Flags ().Bool ("local" , false , "search only local cache (offline)" )
208+ searchCmd .Flags ().Bool ("remote" , false , "force remote API search" )
159209}
0 commit comments