c0d2a4e0025da763e51418085b1ff615bcb0c408
- Author
- TheEdgeOfRage <git@theedgeofrage.com>
- Committer
- TheEdgeOfRage <git@theedgeofrage.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/cmd/jp/main.go b/cmd/jp/main.go
2index db386e59361cb5f90e80100bf80dd69deb48f47e..1b4bb2271a5ceb72e6fd205420deb0abdd20fc66 100644
3--- a/cmd/jp/main.go
4+++ b/cmd/jp/main.go
5@@ -4,7 +4,6 @@ package main
6 import (
7 "context"
8 "encoding/json"
9- "flag"
10 "fmt"
11 "io"
12 "net/http"
13@@ -12,6 +11,7 @@ import (
14 "time"
15
16 "github.com/charmbracelet/bubbletea"
17+ flags "github.com/jessevdk/go-flags"
18
19 "japanese/internal/adapters"
20 "japanese/internal/availability"
21@@ -47,15 +47,37 @@ func main() {
22 // (which enforces it) and to the UI (which shows it as the cap indicator).
23 const recordCap = 10 * time.Second
24
25+// parseOpts parses go-flags options for one subcommand. It prints help and
26+// exits zero on -h/--help; any other parse error prints the error with help
27+// and exits non-zero.
28+func parseOpts[T any](name string, args []string) *T {
29+ opts := new(T)
30+ parser := flags.NewParser(opts, flags.HelpFlag)
31+ _, err := parser.ParseArgs(args)
32+ if err == nil {
33+ return opts
34+ }
35+ if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
36+ parser.WriteHelp(os.Stdout)
37+ os.Exit(0)
38+ }
39+ fmt.Fprintf(os.Stderr, "jp %s: %v\n", name, err)
40+ parser.WriteHelp(os.Stderr)
41+ os.Exit(1)
42+ return nil
43+}
44+
45+type runOptions struct {
46+ config.Config
47+ MapPath string `long:"map" env:"JP_MAP_PATH" default:"assets/maps/city.json" description:"path to the city map JSON"`
48+ Personas string `long:"persona-dir" env:"JP_PERSONA_DIR" default:"assets/personas" description:"directory of persona JSON files"`
49+}
50+
51 func runCmd(args []string) {
52- fs := flag.NewFlagSet("run", flag.ExitOnError)
53- var cfg config.Config
54- cfg.RegisterFlags(fs)
55- mapPath := fs.String("map", "assets/maps/city.json", "path to the city map JSON")
56- personasDir := fs.String("personas", "assets/personas", "directory of persona JSON files")
57- fs.Parse(args)
58-
59- data, err := os.ReadFile(*mapPath)
60+ opts := parseOpts[runOptions]("run", args)
61+ cfg := opts.Config
62+
63+ data, err := os.ReadFile(opts.MapPath)
64 if err != nil {
65 fatalf("read map: %v", err)
66 }
67@@ -63,7 +85,7 @@ func runCmd(args []string) {
68 if err != nil {
69 fatalf("load map: %v", err)
70 }
71- personas, err := persona.LoadDir(*personasDir)
72+ personas, err := persona.LoadDir(opts.Personas)
73 if err != nil {
74 fatalf("load personas: %v", err)
75 }
76@@ -141,23 +163,20 @@ func personaCmd(args []string) {
77 genPersonaCmd(args[1:])
78 }
79
80+type genOptions struct {
81+ config.Config
82+ Role string `long:"role" required:"true" description:"role to author a persona for"`
83+}
84+
85 func genPersonaCmd(args []string) {
86- fs := flag.NewFlagSet("persona gen", flag.ExitOnError)
87- var cfg config.Config
88- cfg.RegisterFlags(fs)
89- role := fs.String("role", "", "Role to author a persona for (required)")
90- fs.Parse(args)
91- if *role == "" {
92- fmt.Fprintln(os.Stderr, "jp persona gen: --role is required")
93- os.Exit(1)
94- }
95+ opts := parseOpts[genOptions]("persona gen", args)
96
97 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
98 defer cancel()
99- client := llm.NewClient(cfg.LLMBaseURL, nil)
100- draft, err := persona.GenerateDraft(ctx, client, *role)
101+ client := llm.NewClient(opts.Config.LLMBaseURL, nil)
102+ draft, err := persona.GenerateDraft(ctx, client, opts.Role)
103 if err != nil {
104- fmt.Fprintf(os.Stderr, "persona gen: LLM service at %s unavailable: %v\n", cfg.LLMBaseURL, err)
105diff --git a/go.mod b/go.mod
106index 052652798fe4a04c7d7d05f343a84af496da3f96..85cb01ed073b6aaa1430fbd9525790285708da95 100644
107--- a/go.mod
108+++ b/go.mod
109@@ -16,6 +16,7 @@ require (
110 github.com/charmbracelet/x/term v0.2.1 // indirect
111 github.com/ebitengine/purego v0.11.0 // indirect
112 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
113+ github.com/jessevdk/go-flags v1.6.1 // indirect
114 github.com/jfreymuth/pulse v0.1.3 // indirect
115 github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
116 github.com/mattn/go-isatty v0.0.20 // indirect
117diff --git a/go.sum b/go.sum
118index 60f01736a33de0ecd0c4d4a9a68077d053bb5ec2..84771d0a0b2a16e68b126abdd8d9686bda887fb5 100644
119--- a/go.sum
120+++ b/go.sum
121@@ -18,6 +18,8 @@ github.com/ebitengine/purego v0.11.0 h1:jhp/D+Nyv7UUW8HAcmcjt2N2rYrYi9m3SL21k0Ua
122 github.com/ebitengine/purego v0.11.0/go.mod h1:DCHPP08djqhNSoTfImcnHYQRZmd0qhakvrozqaEYhGQ=
123 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
124 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
125+github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
126+github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
127 github.com/jfreymuth/pulse v0.1.3 h1:bc5TdxiB8E+2INnFjFWWgyfgXtz2IyNNNCX+Wt/ZD14=
128 github.com/jfreymuth/pulse v0.1.3/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no=
129 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
130diff --git a/internal/config/config.go b/internal/config/config.go
131index cebcaa07692c1975ed95311f7c0e38957a341713..be3225dcbf9ed198b975c6e43d26c8610045f872 100644
132--- a/internal/config/config.go
133+++ b/internal/config/config.go
134@@ -1,47 +1,15 @@
135-// Package config holds endpoint and recorder configuration for the game.
136-// Values come from flags and environment variables; flags win over env vars,
137-// which win over built-in defaults.
138+// Package config holds the service endpoint and recorder configuration shared
139+// by all jp subcommands. Values come from command-line flags, environment
140+// variables, or built-in defaults; flags win over env vars, which win over
141+// defaults.
142 package config
143
144-import (
145- "flag"
146- "os"
147-)
148-
149-const (
150- DefaultLLMBaseURL = "http://127.0.0.1:8081/v1"
151- DefaultTTSBaseURL = "http://127.0.0.1:8080/v1"
152- DefaultSTTURL = "http://127.0.0.1:8178/inference"
153- DefaultSTTLanguage = "auto"
154- DefaultRecordCommand = "arecord"
155-
156- EnvLLMBaseURL = "JP_LLM_BASE_URL"
157- EnvTTSBaseURL = "JP_TTS_BASE_URL"
158- EnvSTTURL = "JP_STT_URL"
159- EnvSTTLanguage = "JP_STT_LANGUAGE"
160-)
161-
162+// Config is the shared option group. Subcommands embed it in their own options
163+// struct and parse with github.com/jessevdk/go-flags.
164 type Config struct {
165- LLMBaseURL string
166- TTSBaseURL string
167- STTURL string
168- STTLanguage string
169- RecordCommand string
170-}
171-
172-func envOrDefault(key, fallback string) string {
173- if value := os.Getenv(key); value != "" {
174- return value
175- }
176- return fallback
177-}
178-
179-// RegisterFlags adds the service endpoint and recorder flags to fs, seeding
180-// each default from the matching environment variable.
181-func (c *Config) RegisterFlags(fs *flag.FlagSet) {
182- fs.StringVar(&c.LLMBaseURL, "llm-url", envOrDefault(EnvLLMBaseURL, DefaultLLMBaseURL), "OpenAI-compatible LLM base URL")
183- fs.StringVar(&c.TTSBaseURL, "tts-url", envOrDefault(EnvTTSBaseURL, DefaultTTSBaseURL), "OpenAI-compatible TTS base URL")
184- fs.StringVar(&c.STTURL, "stt-url", envOrDefault(EnvSTTURL, DefaultSTTURL), "Whisper inference URL")
185- fs.StringVar(&c.STTLanguage, "stt-language", envOrDefault(EnvSTTLanguage, DefaultSTTLanguage), "Optional Whisper request language")
186- fs.StringVar(&c.RecordCommand, "record-command", DefaultRecordCommand, "Recorder command for 16kHz mono S16_LE WAV capture")
187+ LLMBaseURL string `long:"llm-url" env:"JP_LLM_BASE_URL" default:"http://127.0.0.1:8081/v1" description:"OpenAI-compatible LLM base URL"`
188+ TTSBaseURL string `long:"tts-url" env:"JP_TTS_BASE_URL" default:"http://127.0.0.1:8080/v1" description:"OpenAI-compatible TTS base URL"`
189+ STTURL string `long:"stt-url" env:"JP_STT_URL" default:"http://127.0.0.1:8178/inference" description:"Whisper inference URL"`
190+ STTLanguage string `long:"stt-language" env:"JP_STT_LANGUAGE" default:"auto" description:"Optional Whisper request language"`
191+ RecordCommand string `long:"record-command" default:"arecord" description:"Recorder command for 16kHz mono S16_LE WAV capture"`
192 }