Diff
1diff --git a/cmd/jp/main.go b/cmd/jp/main.go
2index f2b292ea93ac9b4dd5fd2eb40c86f07893253223..f4280f829ac62bc6c814b5c3742b343ee2ff3b58 100644
3--- a/cmd/jp/main.go
4+++ b/cmd/jp/main.go
5@@ -167,10 +167,10 @@ func genPersonaCmd(args []string) {
6
7 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
8 defer cancel()
9- client := llm.NewClient(opts.Config.LLMBaseURL, nil)
10+ client := llm.NewClient(opts.LLMBaseURL, nil)
11 draft, err := persona.GenerateDraft(ctx, client, opts.Role)
12 if err != nil {
13- fmt.Fprintf(os.Stderr, "persona gen: LLM service at %s unavailable: %v\n", opts.Config.LLMBaseURL, err)
14+ fmt.Fprintf(os.Stderr, "persona gen: LLM service at %s unavailable: %v\n", opts.LLMBaseURL, err)
15 os.Exit(1)
16 }
17
18@@ -179,13 +179,15 @@ func genPersonaCmd(args []string) {
19 fmt.Fprintf(os.Stderr, "persona gen: encode draft: %v\n", merr)
20 os.Exit(1)
21 }
22- fmt.Fprintln(os.Stdout, string(out))
23+ if _, werr := fmt.Fprintln(os.Stdout, string(out)); werr != nil {
24+ os.Exit(1)
25+ }
26 fmt.Fprintln(os.Stderr, "DRAFT persona. Requires human review before use.")
27 fmt.Fprintln(os.Stderr, "Nothing was written to assets/personas/. Review it and save it yourself.")
28 }
29
30 func usage(w io.Writer) {
31- fmt.Fprint(w, `jp - Japanese learning RPG
32+ _, _ = fmt.Fprint(w, `jp - Japanese learning RPG
33
34 Usage:
35 jp run [flags] Run the game
36diff --git a/internal/availability/availability.go b/internal/availability/availability.go
37index 4d231532767e317441f5d379612dde7976e06175..7d7f401e0def7d6a20e6bea7e43e9827c7aef4b3 100644
38--- a/internal/availability/availability.go
39+++ b/internal/availability/availability.go
40@@ -46,7 +46,7 @@ func CheckAllAsync(cfg config.Config, fn func(Service)) {
41 func checkLLM(ctx context.Context, client *http.Client, base string) Service {
42 s := Service{Name: "LLM", URL: base}
43 modelsURL := strings.TrimRight(base, "/") + "/models"
44- resp, body, err := doGet(ctx, client, modelsURL)
45+ status, body, err := doGet(ctx, client, modelsURL)
46 if err != nil {
47 s.Detail = err.Error()
48 return s
49@@ -55,7 +55,7 @@ func checkLLM(ctx context.Context, client *http.Client, base string) Service {
50 if ids := modelIDs(body); len(ids) > 0 {
51 s.Detail = "models: " + strings.Join(ids, ", ")
52 } else {
53- s.Detail = fmt.Sprintf("HTTP %d", resp.StatusCode)
54+ s.Detail = fmt.Sprintf("HTTP %d", status)
55 }
56 return s
57 }
58@@ -63,16 +63,16 @@ func checkLLM(ctx context.Context, client *http.Client, base string) Service {
59 func checkTTS(ctx context.Context, client *http.Client, base string) Service {
60 s := Service{Name: "TTS", URL: base}
61 modelsURL := strings.TrimRight(base, "/") + "/models"
62- resp, _, err := doGet(ctx, client, modelsURL)
63+ status, _, err := doGet(ctx, client, modelsURL)
64 if err != nil {
65 s.Detail = err.Error()
66 return s
67 }
68 s.Up = true
69- if resp.StatusCode == http.StatusNotFound {
70+ if status == http.StatusNotFound {
71 s.Detail = "server responded; /models not available (HTTP 404)"
72 } else {
73- s.Detail = fmt.Sprintf("HTTP %d", resp.StatusCode)
74+ s.Detail = fmt.Sprintf("HTTP %d", status)
75 }
76 return s
77 }
78@@ -84,32 +84,33 @@ func checkSTT(ctx context.Context, client *http.Client, sttURL string) Service {
79 s.Detail = err.Error()
80 return s
81 }
82- resp, _, err := doGet(ctx, client, root)
83+ status, _, err := doGet(ctx, client, root)
84 if err != nil {
85 s.Detail = err.Error()
86 return s
87 }
88 s.Up = true
89- s.Detail = fmt.Sprintf("HTTP %d", resp.StatusCode)
90+ s.Detail = fmt.Sprintf("HTTP %d", status)
91 return s
92 }
93
94-// doGet issues a bounded GET and returns the response plus its fully read body.
95-func doGet(ctx context.Context, client *http.Client, target string) (*http.Response, []byte, error) {
96+// doGet issues a bounded GET and returns the status code plus the fully read
97+// body.
98+func doGet(ctx context.Context, client *http.Client, target string) (int, []byte, error) {
99 req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)
100 if err != nil {
101- return nil, nil, err
102+ return 0, nil, err
103 }
104 resp, err := client.Do(req)
105 if err != nil {
106- return nil, nil, err
107+ return 0, nil, err
108 }
109- defer resp.Body.Close()
110+ defer func() { _ = resp.Body.Close() }()
111 body, readErr := io.ReadAll(resp.Body)
112 if readErr != nil {
113- return resp, nil, readErr
114+ return 0, nil, readErr
115 }
116- return resp, body, nil
117+ return resp.StatusCode, body, nil
118 }
119
120 // hostRoot reduces a full inference URL to its scheme://host/ so the STT check
121diff --git a/internal/llm/client.go b/internal/llm/client.go
122index 31b20d4cd2ddd044f0ec694b67fdf2db2e125d55..fc6551c9454fb7afcd17a950b329ea6782e9c5cc 100644
123--- a/internal/llm/client.go
124+++ b/internal/llm/client.go
125@@ -101,7 +101,7 @@ func (c *Client) Generate(ctx context.Context, msgs []Message, opts Options) (st
126 if err != nil {
127 return "", fmt.Errorf("llm: request to %s: %w", url, err)
128 }
129- defer resp.Body.Close()
130+ defer func() { _ = resp.Body.Close() }()
131
132 if resp.StatusCode < 200 || resp.StatusCode >= 300 {
133 detail, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
134diff --git a/internal/stt/whisper.go b/internal/stt/whisper.go
135index 51707374635383736ef5d5bf7b0f92f29e9651e9..9a69be166e7c955e465219d3a4ae75270cce14ec 100644
136--- a/internal/stt/whisper.go
137+++ b/internal/stt/whisper.go
138@@ -37,7 +37,7 @@ func (c *WhisperClient) Transcribe(ctx context.Context, wavPath string) (string,
139 if err != nil {
140 return "", fmt.Errorf("stt: read recording %s: %w", wavPath, err)
141 }
142- defer os.Remove(wavPath)
143+ defer func() { _ = os.Remove(wavPath) }()
144
145 body, contentType, err := encodeMultipart(data, c.Language)
146 if err != nil {
147@@ -53,7 +53,7 @@ func (c *WhisperClient) Transcribe(ctx context.Context, wavPath string) (string,
148 if err != nil {
149 return "", fmt.Errorf("stt: request to %s: %w", c.URL, err)
150 }
151- defer resp.Body.Close()
152+ defer func() { _ = resp.Body.Close() }()
153
154 raw, err := io.ReadAll(resp.Body)
155 if err != nil {
156diff --git a/internal/tts/client.go b/internal/tts/client.go
157index c3bb07839f16c18485a9bff5ac451dc81d088424..6c28149a94513e31d299d09061f6f1470127789f 100644
158--- a/internal/tts/client.go
159+++ b/internal/tts/client.go
160@@ -70,7 +70,7 @@ func (c *Client) Speech(ctx context.Context, kana string) ([]byte, error) {
161 if err != nil {
162 return nil, fmt.Errorf("tts: request to %s: %w", url, err)
163 }
164- defer resp.Body.Close()
165+ defer func() { _ = resp.Body.Close() }()
166
167 raw, err := io.ReadAll(resp.Body)
168 if err != nil {
169diff --git a/internal/tts/player.go b/internal/tts/player.go
170index d603e58dd1adce103497bdf359f7222e6c5c03f9..de4b16c55a8a99f6277122b6fa8bfaa849f168f6 100644
171--- a/internal/tts/player.go
172+++ b/internal/tts/player.go
173@@ -14,12 +14,12 @@ import (
174 // later clips are played through that same context (oto supports a single
175 // context). Play validates the WAV synchronously and returns any error from
176 // that check; device work happens off the calling goroutine so the TUI event
177-// loop is never blocked. A new Play interrupts an in-flight playback, and Stop
178-// interrupts one explicitly.
179+// loop is never blocked. A new Play interrupts an in-flight playback.
180 //
181 // Each Play takes an onDone callback that fires exactly once when that clip
182-// finishes playing normally. An interrupted clip (a newer Play, or Stop) never
183-// fires onDone, so callers can distinguish "played to the end" from "cut off".
184+// finishes playing normally. An interrupted clip (superseded by a newer Play)
185+// never fires onDone, so callers can distinguish "played to the end" from
186+// "cut off".
187 type Player struct {
188 mu sync.Mutex
189 ctx *oto.Context
190@@ -84,12 +84,10 @@ func (p *Player) play(ctx context.Context, spec *WAVSpec, onDone func()) {
191 select {
192 case <-ctx.Done():
193 pl.PauseAndStopReading()
194- pl.Close()
195 return
196 case <-time.After(20 * time.Millisecond):
197 }
198 }
199- pl.Close()
200 finish()
201 }
202