Diff
1diff --git a/README.md b/README.md
2index e3a51b5c3dcf3345e551cc0721bd339a76bd6515..d0d32467b1c31191d6012b49396dc43acd5b7c9a 100644
3--- a/README.md
4+++ b/README.md
5@@ -26,7 +26,7 @@ downloads a model for any of them. You run those services yourself (see
6
7 ```bash
8 go build ./...
9-jp run # or: go run ./cmd/jp run
10+jp # or: go run ./cmd/jp
11 ```
12
13 On startup the TUI appears immediately. A bounded HTTP readiness check runs per
14@@ -55,7 +55,7 @@ Content paths:
15 | `--map` | `JP_MAP_PATH` | `assets/maps/city.json` |
16 | `--persona-dir` | `JP_PERSONA_DIR` | `assets/personas` |
17
18-Run `jp run -help` for the full flag list.
19+Run `jp -help` for the full flag list.
20
21 ## External services
22
23@@ -96,7 +96,7 @@ Prerequisites, all run by you (the game never starts or stops them):
24
25 Exact reference commands are in [`docs/services.md`](docs/services.md).
26
27-1. Run `jp run`. The TUI appears immediately; each service line flips from
28+1. Run `jp`. The TUI appears immediately; each service line flips from
29 `checking` to `up` as its probe result arrives.
30 2. Walk to the ramen shop marker and confirm it is marked active in the legend.
31 3. Press `space`, speak a Japanese line, press `space` again. Your transcript
32diff --git a/cmd/jp/main.go b/cmd/jp/main.go
33index 79166aef5fa4dba67eff0717fff29fbf5dd4ae79..46e1a61e7525ee3cb5784311d69363533796b8a8 100644
34--- a/cmd/jp/main.go
35+++ b/cmd/jp/main.go
36@@ -3,7 +3,6 @@ package main
37
38 import (
39 "fmt"
40- "io"
41 "net/http"
42 "os"
43 "time"
44@@ -22,50 +21,22 @@ import (
45 "japanese/internal/ui"
46 )
47
48-func main() {
49- if len(os.Args) < 2 {
50- usage(os.Stderr)
51- os.Exit(1)
52- }
53- switch os.Args[1] {
54- case "run":
55- runCmd(os.Args[2:])
56- case "help", "-h", "--help":
57- usage(os.Stdout)
58- default:
59- fmt.Fprintf(os.Stderr, "jp: unknown command %q\n\n", os.Args[1])
60- usage(os.Stderr)
61- os.Exit(1)
62- }
63-}
64-
65 // recordCap bounds a single push-to-talk capture. It is passed to the recorder
66 // (which enforces it) and to the UI (which shows it as the cap indicator).
67 const recordCap = 10 * time.Second
68
69-// parseOpts parses go-flags options for one subcommand. It prints help and
70-// exits zero on -h/--help; any other parse error prints the error with help
71-// and exits non-zero.
72-func parseOpts[T any](name string, args []string) *T {
73- opts := new(T)
74- parser := flags.NewParser(opts, flags.HelpFlag)
75- _, err := parser.ParseArgs(args)
76- if err == nil {
77- return opts
78- }
79- if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
80- parser.WriteHelp(os.Stdout)
81- os.Exit(0)
82+func main() {
83+ var cfg config.Config
84+ parser := flags.NewParser(&cfg, flags.HelpFlag)
85+ if _, err := parser.ParseArgs(os.Args[1:]); err != nil {
86+ if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
87+ parser.WriteHelp(os.Stdout)
88+ os.Exit(0)
89+ }
90+ fmt.Fprintf(os.Stderr, "jp: %v\n", err)
91+ parser.WriteHelp(os.Stderr)
92+ os.Exit(1)
93 }
94- fmt.Fprintf(os.Stderr, "jp %s: %v\n", name, err)
95- parser.WriteHelp(os.Stderr)
96- os.Exit(1)
97- return nil
98-}
99-
100-func runCmd(args []string) {
101- opts := parseOpts[config.Config]("run", args)
102- cfg := *opts
103
104 data, err := os.ReadFile(cfg.MapPath)
105 if err != nil {
106@@ -90,7 +61,7 @@ func runCmd(args []string) {
107 p.Send(ui.ProbeResultMsg{Svc: svc})
108 })
109 if _, err := p.Run(); err != nil {
110- fatalf("run: %v", err)
111+ fatalf("%v", err)
112 }
113 }
114
115@@ -141,17 +112,6 @@ func composeDescription(p persona.Persona) string {
116 }
117
118 func fatalf(format string, args ...any) {
119- fmt.Fprintf(os.Stderr, "jp run: "+format+"\n", args...)
120+ fmt.Fprintf(os.Stderr, "jp: "+format+"\n", args...)
121 os.Exit(1)
122 }
123-
124-func usage(w io.Writer) {
125- _, _ = fmt.Fprint(w, `jp - Japanese learning RPG
126-
127-Usage:
128- jp run [flags] Run the game
129- jp help Show this help
130-
131-Run "jp run -help" for flag details.
132-`)
133-}