tryInvokeUserSkill inspects userInput for a `/ [args]` pattern and, if ` ` resolves to a user-invocable skill, stages the skill for the next LLM turn and kicks off processLLMRequest. Returns true when the input was recognized (so the caller's default "unknown command" branch should not fi
(ctx context.Context, userInput string)
| 49 | // was recognized (so the caller's default "unknown command" branch should |
| 50 | // not fire); returns false when the input is not a skill invocation. |
| 51 | func (ch *CommandHandler) tryInvokeUserSkill(ctx context.Context, userInput string) bool { |
| 52 | if !strings.HasPrefix(userInput, "/") || len(userInput) < 2 { |
| 53 | return false |
| 54 | } |
| 55 | if ch.cli.personaHandler == nil { |
| 56 | return false |
| 57 | } |
| 58 | mgr := ch.cli.personaHandler.GetManager() |
| 59 | if mgr == nil { |
| 60 | return false |
| 61 | } |
| 62 | |
| 63 | // Split into "/name" + rest. |
| 64 | trimmed := strings.TrimPrefix(userInput, "/") |
| 65 | parts := strings.SplitN(trimmed, " ", 2) |
| 66 | name := strings.TrimSpace(parts[0]) |
| 67 | if name == "" || reservedSlashCommands[strings.ToLower(name)] { |
| 68 | return false |
| 69 | } |
| 70 | |
| 71 | skill, err := mgr.GetSkillByName(name) |
| 72 | if err != nil || skill == nil { |
| 73 | return false |
| 74 | } |
| 75 | if !skill.UserInvocable { |
| 76 | fmt.Println(colorize( |
| 77 | fmt.Sprintf(" %s: /%s", i18n.T("skill.invoke.not_invocable"), name), |
| 78 | ColorYellow)) |
| 79 | return true // recognized name but refused — don't fall through to "unknown command" |
| 80 | } |
| 81 | |
| 82 | args := "" |
| 83 | if len(parts) > 1 { |
| 84 | args = strings.TrimSpace(parts[1]) |
| 85 | } |
| 86 | |
| 87 | // If the client isn't configured yet we can't actually run the turn. |
| 88 | if ch.cli.Client == nil { |
| 89 | fmt.Println(i18n.T("cli.error.no_provider_configured")) |
| 90 | return true |
| 91 | } |
| 92 | |
| 93 | // Show argument-hint when the user called the skill with no args. |
| 94 | if args == "" && skill.ArgumentHint != "" { |
| 95 | fmt.Printf(" %s %s\n", |
| 96 | colorize("hint:", ColorGray), |
| 97 | colorize(skill.ArgumentHint, ColorCyan)) |
| 98 | } |
| 99 | |
| 100 | // Stage the skill for the next processLLMRequest call. It is cleared |
| 101 | // inside processLLMRequest after injection so it only affects this turn. |
| 102 | ch.cli.pendingManualSkill = skill |
| 103 | ch.cli.pendingManualSkillArgs = args |
| 104 | |
| 105 | // Synthesize the user-visible prompt. When the user passes no args we |
| 106 | // emit a neutral instruction that preserves intent without guessing. |
| 107 | prompt := args |
| 108 | if prompt == "" { |
no test coverage detected