mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
feat(command): extend slash command system with new commands and UX improvements
Add 9 new command groups (/model, /memory, /search, /browser, /usage, /email, /heartbeat, /skill, /fs) and improve existing commands by hiding internal UUIDs, resolving IDs to human-readable names in /settings, and switching /schedule to name-based references.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParsedCommand holds the parsed components of a slash command.
|
||||
type ParsedCommand struct {
|
||||
Resource string // e.g. "schedule", "subagent", "help"
|
||||
Action string // e.g. "list", "get", "create"
|
||||
Args []string // remaining positional arguments
|
||||
}
|
||||
|
||||
// Parse parses a raw command string into its components.
|
||||
// Expected format: /resource [action] [args...]
|
||||
func Parse(text string) (ParsedCommand, error) {
|
||||
text = strings.TrimSpace(text)
|
||||
if !strings.HasPrefix(text, "/") {
|
||||
return ParsedCommand{}, errors.New("command must start with /")
|
||||
}
|
||||
text = text[1:] // strip leading /
|
||||
|
||||
tokens := tokenize(text)
|
||||
if len(tokens) == 0 {
|
||||
return ParsedCommand{}, errors.New("empty command")
|
||||
}
|
||||
|
||||
resource := strings.ToLower(tokens[0])
|
||||
// Strip Telegram-style @botname suffix (e.g. "help@MemohBot" -> "help").
|
||||
if idx := strings.IndexByte(resource, '@'); idx > 0 {
|
||||
resource = resource[:idx]
|
||||
}
|
||||
|
||||
cmd := ParsedCommand{
|
||||
Resource: resource,
|
||||
}
|
||||
if len(tokens) > 1 {
|
||||
cmd.Action = strings.ToLower(tokens[1])
|
||||
}
|
||||
if len(tokens) > 2 {
|
||||
cmd.Args = tokens[2:]
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// ExtractCommandText finds and extracts a slash command from text that may
|
||||
// contain a leading @mention (e.g. "@BotName /help arg1" -> "/help arg1").
|
||||
// Returns the command text starting with "/", or empty string if none found.
|
||||
func ExtractCommandText(text string) string {
|
||||
trimmed := strings.TrimSpace(text)
|
||||
if strings.HasPrefix(trimmed, "/") {
|
||||
return trimmed
|
||||
}
|
||||
// Look for " /" pattern — a slash preceded by whitespace.
|
||||
idx := strings.Index(trimmed, " /")
|
||||
if idx >= 0 {
|
||||
return strings.TrimSpace(trimmed[idx+1:])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// tokenize splits a command string respecting quoted segments.
|
||||
func tokenize(input string) []string {
|
||||
var tokens []string
|
||||
var current strings.Builder
|
||||
inQuote := false
|
||||
quoteChar := byte(0)
|
||||
|
||||
for i := 0; i < len(input); i++ {
|
||||
ch := input[i]
|
||||
if inQuote {
|
||||
if ch == quoteChar {
|
||||
inQuote = false
|
||||
continue
|
||||
}
|
||||
current.WriteByte(ch)
|
||||
continue
|
||||
}
|
||||
if ch == '"' || ch == '\'' {
|
||||
inQuote = true
|
||||
quoteChar = ch
|
||||
continue
|
||||
}
|
||||
if ch == ' ' || ch == '\t' {
|
||||
if current.Len() > 0 {
|
||||
tokens = append(tokens, current.String())
|
||||
current.Reset()
|
||||
}
|
||||
continue
|
||||
}
|
||||
current.WriteByte(ch)
|
||||
}
|
||||
if current.Len() > 0 {
|
||||
tokens = append(tokens, current.String())
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
Reference in New Issue
Block a user