@@ -14,6 +14,7 @@ import (
1414 "github.com/yeasy/ask/internal/github"
1515 "github.com/yeasy/ask/internal/repository"
1616 "github.com/yeasy/ask/internal/skill"
17+ "github.com/yeasy/ask/internal/skillhub"
1718)
1819
1920// installCmd represents the install command
@@ -268,11 +269,66 @@ func installSingleSkill(input string, global bool, agents []string) error {
268269 } else {
269270 // It's a URL (e.g., https://github.com/xxx.git)
270271 repoURL = input
272+ if ! strings .HasSuffix (repoURL , ".git" ) && ! strings .HasPrefix (input , "http" ) {
273+ // It might be a SkillHub slug (no standard user/repo format easily distinguishable from just a name,
274+ // but usually slugs are kebab-case).
275+ // We can try to resolve it via SkillHub if it doesn't look like a URL.
276+ // However, `input` here in this block is usually "http..." because of `isURL` check?
277+ // Wait, `isURL` is true for http/git prefix.
278+ }
271279 urlParts := strings .Split (strings .TrimSuffix (repoURL , ".git" ), "/" )
272280 skillName = urlParts [len (urlParts )- 1 ]
273281 }
274282 }
275283
284+ // SkillHub Slug Resolution
285+ // If it wasn't a valid GitHub URL and resolved to just a "name" or if we want to support direct slug install:
286+ // "ask skill install madappgang-claude-code-python"
287+ // This falls into the "else" of "check if input matches configured repository name" in the caller `Run` loop?
288+ // The `Run` loop checks configured repos. If not found, it passes `input` directly to `installSingleSkill`.
289+ // So `installSingleSkill` receives "madappgang-claude-code-python".
290+ // It goes to `else { Check if it's a direct URL or shorthand }`.
291+ // `isURL` = false.
292+ // `parts := strings.Split(input, "/")` -> len=1.
293+ // So it falls to `else { Standard install: owner/repo }` ? NO.
294+ // The code assumes input is "owner/repo" if it splits to 2?
295+ // Wait, let's look at `installSingleSkill` logic again.
296+
297+ /*
298+ if !isURL {
299+ parts := strings.Split(input, "/")
300+ if len(parts) > 2 {
301+ // subdir
302+ } else {
303+ // owner/repo
304+ repoURL = "https://github.com/" + input
305+ }
306+ }
307+ */
308+
309+ // If input is "slug", `parts` has len 1.
310+ // The code `else { owner/repo }` logic (lines 262-267) assumes `len(parts) <= 2` handles owner/repo.
311+ // But if `len(parts) == 1`, `repoURL` becomes `https://github.com/slug`.
312+ // This is valid for GitHub user profile or org, but not a repo.
313+
314+ // We need to inject logic: if it looks like a slug (and not owner/repo), try SkillHub resolve.
315+ // Or try SkillHub resolve if GitHub check fails?
316+ // Doing it optimistically: if `strings.Contains(input, "/")` is false, it might be a slug.
317+
318+ if ! strings .Contains (input , "/" ) && ! strings .HasPrefix (input , "http" ) && ! strings .HasPrefix (input , "git" ) {
319+ // Try resolving as SkillHub slug
320+ client := skillhub .NewClient ()
321+ if resolved , err := client .Resolve (input ); err == nil {
322+ fmt .Printf ("Resolved SkillHub slug '%s' to '%s'\n " , input , resolved )
323+ // Recursive call or set variables?
324+ // URL found (e.g. https://github.com/owner/repo#...)
325+ // Recursing is easiest to handle the new URL format (which might be a tree URL).
326+ return installSingleSkill (resolved , global , agents )
327+ }
328+ // If resolve fails, we proceed (it might be a local directory or something, though `ask` doesn't strictly support local paths yet in this function).
329+ // Or it falls through to be treated as `github.com/input` which will fail.
330+ }
331+
276332 // Use branch from version if not set from URL parsing
277333 if branch == "" && version != "" {
278334 branch = version
0 commit comments