// Package main — strutil.go holds small, receiver-free string utilities used // across the voice reply paths: trimming a wake token, pulling out the first // word or first line, and a minimal JSON string encoder for the one payload // shape that needs it. Extend this file rather than voice.go for anything in // that shape. package main import ( "fmt" "strings" "github.com/kami/maven/internal/router" ) // stripWake removes a leading wake token (any script the STT phonetically // transcribes "Maven" as) so the verb is the first word. func stripWake(u string) string { stripped, had := router.StripWakeToken(u) if !had { return strings.TrimSpace(u) } return stripped } // firstWord returns the first whitespace-delimited token (lowercased) — the // proposed tool's name. func firstWord(s string) string { f := strings.Fields(s) if len(f) == 0 { return "" } return strings.ToLower(f[0]) } // firstLine — the first non-empty line of a tool's output, for a short spoken // reply (the full output goes to the log, not the TTS). Trimmed to keep the // utterance sane if a command dumps a wall of text. func firstLine(s string) string { for _, line := range strings.Split(s, "\n") { line = strings.TrimSpace(line) if line != "" { if len(line) > 200 { line = line[:200] } return line } } return "" } // jsonString — a one-line JSON string encoder without dragging encoding/json // into the top of this file. Used to wrap a reminder payload's text field; // the router's reminder Slots are already absolute (DateTimeParser resolved // relative→absolute), the payload shape is conventional {"text":...}. func jsonString(s string) string { // minimal JSON string escape — quotes + backslash + control chars. // adequate for the reminder payload's text field; not a general JSON // encoder. The chroma / RAG modules (when they land) use a real json // encoder for richer payloads. Keep it inline here so the import // direction stays narrow. var b []byte b = append(b, '"') for _, r := range s { switch r { case '"': b = append(b, '\\', '"') case '\\': b = append(b, '\\', '\\') case '\n': b = append(b, '\\', 'n') case '\r': b = append(b, '\\', 'r') case '\t': b = append(b, '\\', 't') default: if r < 0x20 { b = append(b, []byte(fmt.Sprintf("\\u%04x", r))...) } else { b = append(b, []byte(string(r))...) } } } b = append(b, '"') return string(b) }