repos / git-pr

a self-hosted git collaboration server
git clone https://github.com/picosh/git-pr.git

commit
3e46541
parent
33ea8c5
author
Eric Bower
date
2026-02-24 10:18:44 -0500 EST
refactor(ssh): replace wish with pssh
10 files changed,  +329, -255
M cfg.go
M cli.go
M go.mod
M go.sum
M mdw.go
M ssh.go
M CHANGELOG.md
+4, -0
 1@@ -4,6 +4,10 @@ Use spec: https://common-changelog.org/
 2 
 3 ## Staged
 4 
 5+### Changed
 6+
 7+- Removed charm's `wish` with pico's `pssh`
 8+
 9 ## v2026-02-24
10 
11 ### Changed
M cfg.go
+1, -0
1@@ -22,6 +22,7 @@ type GitCfg struct {
2 	Host       string          `koanf:"host"`
3 	SshPort    string          `koanf:"ssh_port"`
4 	WebPort    string          `koanf:"web_port"`
5+	PromPort   string          `koanf:"prom_port"`
6 	AdminsStr  []string        `koanf:"admins"`
7 	Admins     []ssh.PublicKey `koanf:"admins_pk"`
8 	CreateRepo string          `koanf:"create_repo"`
M cli.go
+33, -35
  1@@ -8,8 +8,7 @@ import (
  2 	"strings"
  3 	"text/tabwriter"
  4 
  5-	"github.com/charmbracelet/ssh"
  6-	"github.com/charmbracelet/wish"
  7+	"github.com/picosh/pico/pkg/pssh"
  8 	"github.com/urfave/cli/v2"
  9 )
 10 
 11@@ -45,22 +44,22 @@ func getPatchsetFromOpt(patchsets []*Patchset, optPatchsetID string) (*Patchset,
 12 	return nil, fmt.Errorf("cannot find patchset: %s", optPatchsetID)
 13 }
 14 
 15-func printPatches(sesh ssh.Session, patches []*Patch) {
 16+func printPatches(sesh *pssh.SSHServerConnSession, patches []*Patch) {
 17 	if len(patches) == 1 {
 18-		wish.Println(sesh, patches[0].RawText)
 19+		sesh.Println(patches[0].RawText)
 20 		return
 21 	}
 22 
 23 	opatches := patches
 24 	for idx, patch := range opatches {
 25-		wish.Println(sesh, patch.RawText)
 26+		sesh.Println(patch.RawText)
 27 		if idx < len(patches)-1 {
 28-			wish.Printf(sesh, "\n\n\n")
 29+			sesh.Printf("\n\n\n")
 30 		}
 31 	}
 32 }
 33 
 34-func prSummary(be *Backend, pr GitPatchRequest, sesh ssh.Session, prID int64) error {
 35+func prSummary(be *Backend, pr GitPatchRequest, sesh *pssh.SSHServerConnSession, prID int64) error {
 36 	request, err := pr.GetPatchRequestByID(prID)
 37 	if err != nil {
 38 		return err
 39@@ -76,9 +75,9 @@ func prSummary(be *Backend, pr GitPatchRequest, sesh ssh.Session, prID int64) er
 40 		return err
 41 	}
 42 
 43-	wish.Printf(sesh, "Info\n====\n")
 44-	wish.Printf(sesh, "URL: https://%s/prs/%d\n", be.Cfg.Url, prID)
 45-	wish.Printf(sesh, "Repo: %s\n\n", be.CreateRepoNs(repoUser.Name, repo.Name))
 46+	sesh.Printf("Info\n====\n")
 47+	sesh.Printf("URL: https://%s/prs/%d\n", be.Cfg.Url, prID)
 48+	sesh.Printf("Repo: %s\n\n", be.CreateRepoNs(repoUser.Name, repo.Name))
 49 
 50 	writer := NewTabWriter(sesh)
 51 	_, _ = fmt.Fprintln(writer, "ID\tName\tStatus\tDate")
 52@@ -94,7 +93,7 @@ func prSummary(be *Backend, pr GitPatchRequest, sesh ssh.Session, prID int64) er
 53 		return err
 54 	}
 55 
 56-	wish.Printf(sesh, "\nPatchsets\n====\n")
 57+	sesh.Printf("\nPatchsets\n====\n")
 58 
 59 	writerSet := NewTabWriter(sesh)
 60 	_, _ = fmt.Fprintln(writerSet, "ID\tType\tUser\tDate")
 61@@ -130,7 +129,7 @@ func prSummary(be *Backend, pr GitPatchRequest, sesh ssh.Session, prID int64) er
 62 		return err
 63 	}
 64 
 65-	wish.Printf(sesh, "\nPatches from latest patchset\n====\n")
 66+	sesh.Printf("\nPatches from latest patchset\n====\n")
 67 
 68 	opatches := patches
 69 	w := NewTabWriter(sesh)
 70@@ -152,7 +151,7 @@ func prSummary(be *Backend, pr GitPatchRequest, sesh ssh.Session, prID int64) er
 71 	return nil
 72 }
 73 
 74-func printPatchsetFromID(sesh ssh.Session, pr GitPatchRequest, psID int64) error {
 75+func printPatchsetFromID(sesh *pssh.SSHServerConnSession, pr GitPatchRequest, psID int64) error {
 76 	patches, err := pr.GetPatchesByPatchsetID(psID)
 77 	if err != nil {
 78 		return err
 79@@ -161,7 +160,7 @@ func printPatchsetFromID(sesh ssh.Session, pr GitPatchRequest, psID int64) error
 80 	return nil
 81 }
 82 
 83-func printPatchsetFromPrID(sesh ssh.Session, pr GitPatchRequest, prID int64) error {
 84+func printPatchsetFromPrID(sesh *pssh.SSHServerConnSession, pr GitPatchRequest, prID int64) error {
 85 	patchsets, err := pr.GetPatchsetsByPrID(prID)
 86 	if err != nil {
 87 		return err
 88@@ -176,7 +175,7 @@ func printPatchsetFromPrID(sesh ssh.Session, pr GitPatchRequest, prID int64) err
 89 	return nil
 90 }
 91 
 92-func NewCli(sesh ssh.Session, be *Backend, pr GitPatchRequest) *cli.App {
 93+func NewCli(sesh *pssh.SSHServerConnSession, be *Backend, pr GitPatchRequest) *cli.App {
 94 	desc := fmt.Sprintf(`git-pr (v%s): A pastebin supercharged for git collaboration.
 95 
 96 Here's how it works:
 97@@ -209,12 +208,12 @@ To get started, submit a new patch request:
 98 		ErrWriter:   sesh,
 99 		ExitErrHandler: func(cCtx *cli.Context, err error) {
100 			if err != nil {
101-				wish.Fatalln(sesh, fmt.Errorf("err: %w", err))
102+				sesh.Fatal(fmt.Errorf("err: %w", err))
103 			}
104 		},
105 		OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error {
106 			if err != nil {
107-				wish.Fatalln(sesh, fmt.Errorf("err: %w", err))
108+				sesh.Fatal(fmt.Errorf("err: %w", err))
109 			}
110 			return nil
111 		},
112@@ -305,7 +304,7 @@ To get started, submit a new patch request:
113 					if err != nil {
114 						return err
115 					}
116-					wish.Printf(sesh, "User created successfully!\nUser: %s\nPubkey: %s\n", user.Name, pubkey)
117+					sesh.Printf("User created successfully!\nUser: %s\nPubkey: %s\n", user.Name, pubkey)
118 					return nil
119 				},
120 			},
121@@ -350,7 +349,7 @@ To get started, submit a new patch request:
122 							if err != nil {
123 								return err
124 							}
125-							wish.Printf(sesh, "successfully removed patchset: %d\n", patchsetID)
126+							sesh.Printf("successfully removed patchset: %d\n", patchsetID)
127 							return nil
128 						},
129 					},
130@@ -389,7 +388,7 @@ To get started, submit a new patch request:
131 								}
132 							}
133 
134-							wish.Printf(sesh, "repo created: %s/%s", user.Name, repo.Name)
135+							sesh.Printf("repo created: %s/%s", user.Name, repo.Name)
136 							return nil
137 						},
138 					},
139@@ -591,8 +590,7 @@ To get started, submit a new patch request:
140 							if err != nil {
141 								return err
142 							}
143-							wish.Println(
144-								sesh,
145+							sesh.Println(
146 								"PR submitted! Use the ID for interacting with this PR.",
147 							)
148 
149@@ -641,7 +639,7 @@ To get started, submit a new patch request:
150 							for _, prIDStr := range prIDs {
151 								prID, err := strToInt(prIDStr)
152 								if err != nil {
153-									wish.Errorln(sesh, err)
154+									sesh.Errorln(err)
155 									continue
156 								}
157 
158@@ -673,12 +671,12 @@ To get started, submit a new patch request:
159 								if err != nil {
160 									return err
161 								}
162-								wish.Printf(sesh, "Accepted PR %s (#%d)\n", prq.Name, prq.ID)
163+								sesh.Printf("Accepted PR %s (#%d)\n", prq.Name, prq.ID)
164 								err = prSummary(be, pr, sesh, prID)
165 								if err != nil {
166 									errs = errors.Join(errs, err)
167 								}
168-								wish.Printf(sesh, "\n\n")
169+								sesh.Printf("\n\n")
170 							}
171 
172 							return errs
173@@ -708,7 +706,7 @@ To get started, submit a new patch request:
174 							for _, prIDStr := range prIDs {
175 								prID, err := strToInt(prIDStr)
176 								if err != nil {
177-									wish.Errorln(sesh, err)
178+									sesh.Errorln(err)
179 									continue
180 								}
181 
182@@ -745,12 +743,12 @@ To get started, submit a new patch request:
183 								if err != nil {
184 									return err
185 								}
186-								wish.Printf(sesh, "Closed PR %s (#%d)\n", prq.Name, prq.ID)
187+								sesh.Printf("Closed PR %s (#%d)\n", prq.Name, prq.ID)
188 								err = prSummary(be, pr, sesh, prID)
189 								if err != nil {
190 									errs = errors.Join(errs, err)
191 								}
192-								wish.Printf(sesh, "\n\n")
193+								sesh.Printf("\n\n")
194 							}
195 							return errs
196 						},
197@@ -808,7 +806,7 @@ To get started, submit a new patch request:
198 
199 							err = pr.UpdatePatchRequestStatus(prID, user.ID, StatusOpen, cCtx.String("comment"))
200 							if err == nil {
201-								wish.Printf(sesh, "Reopened PR %s (#%d)\n", prq.Name, prq.ID)
202+								sesh.Printf("Reopened PR %s (#%d)\n", prq.Name, prq.ID)
203 							}
204 							return prSummary(be, pr, sesh, prID)
205 						},
206@@ -860,7 +858,7 @@ To get started, submit a new patch request:
207 								title,
208 							)
209 							if err == nil {
210-								wish.Printf(sesh, "New title: %s (%d)\n", title, prq.ID)
211+								sesh.Printf("New title: %s (%d)\n", title, prq.ID)
212 							}
213 
214 							return err
215@@ -930,14 +928,14 @@ To get started, submit a new patch request:
216 							op := OpNormal
217 							nextStatus := StatusOpen
218 							if isReview {
219-								wish.Println(sesh, "Marking patchset as a review")
220+								sesh.Println("Marking patchset as a review")
221 								op = OpReview
222 							} else if isAccept {
223-								wish.Println(sesh, "Marking PR as accepted")
224+								sesh.Println("Marking PR as accepted")
225 								nextStatus = StatusAccepted
226 								op = OpAccept
227 							} else if isClose {
228-								wish.Println(sesh, "Marking PR as closed")
229+								sesh.Println("Marking PR as closed")
230 								nextStatus = StatusClosed
231 								op = OpClose
232 							}
233@@ -948,7 +946,7 @@ To get started, submit a new patch request:
234 							}
235 
236 							if len(patches) == 0 {
237-								wish.Println(sesh, "Patches submitted! However none were saved, probably because they already exist in the system")
238+								sesh.Println("Patches submitted! However none were saved, probably because they already exist in the system")
239 								return nil
240 							}
241 
242@@ -959,7 +957,7 @@ To get started, submit a new patch request:
243 								}
244 							}
245 
246-							wish.Println(sesh, "Patches submitted!")
247+							sesh.Println("Patches submitted!")
248 							return prSummary(be, pr, sesh, prID)
249 						},
250 					},
M cmd/git-pr/main.go
+19, -16
 1@@ -9,7 +9,6 @@ import (
 2 	"os"
 3 	"os/signal"
 4 	"syscall"
 5-	"time"
 6 
 7 	git "github.com/picosh/git-pr"
 8 )
 9@@ -26,15 +25,6 @@ func main() {
10 	git.LoadConfigFile(*fpath, logger)
11 	cfg := git.NewGitCfg(logger)
12 
13-	// SSH Server
14-	ssh := git.GitSshServer(cfg)
15-	cfg.Logger.Info("starting SSH server", "host", cfg.Host, "port", cfg.SshPort)
16-	go func() {
17-		if err := ssh.ListenAndServe(); err != nil {
18-			cfg.Logger.Error("serve error", "err", err)
19-		}
20-	}()
21-
22 	// Web Server
23 	addr := fmt.Sprintf("%s:%s", cfg.Host, cfg.WebPort)
24 	web := git.GitWebServer(cfg)
25@@ -45,13 +35,26 @@ func main() {
26 		}
27 	}()
28 
29+	ctx, cancel := context.WithCancel(context.Background())
30+	defer cancel()
31+	// SSH Server
32+	ssh := git.GitSshServer(ctx, cfg)
33+
34 	done := make(chan os.Signal, 1)
35 	signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
36-	<-done
37-	cfg.Logger.Info("stopping SSH server")
38-	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
39-	defer func() { cancel() }()
40-	if err := ssh.Shutdown(ctx); err != nil {
41-		cfg.Logger.Error("shutdown", "err", err)
42+	logger.Info("starting SSH server", "addr", ssh.Config.ListenAddr)
43+	go func() {
44+		if err := ssh.ListenAndServe(); err != nil {
45+			logger.Error("serve", "err", err.Error())
46+			os.Exit(1)
47+		}
48+	}()
49+
50+	exit := func() {
51+		logger.Info("stopping ssh server")
52+		cancel()
53 	}
54+
55+	<-done
56+	exit()
57 }
M contrib/dev/main.go
+4, -1
 1@@ -1,6 +1,7 @@
 2 package main
 3 
 4 import (
 5+	"context"
 6 	"flag"
 7 	"fmt"
 8 	"log/slog"
 9@@ -38,7 +39,9 @@ func main() {
10 	git.LoadConfigFile(cfgPath, logger)
11 	cfg := git.NewGitCfg(logger)
12 
13-	s := git.GitSshServer(cfg)
14+	ctx, cancel := context.WithCancel(context.Background())
15+	defer cancel()
16+	s := git.GitSshServer(ctx, cfg)
17 	go func() {
18 		_ = s.ListenAndServe()
19 	}()
M e2e_test.go
+6, -6
 1@@ -24,7 +24,9 @@ func testSingleTenantE2E(t *testing.T) {
 2 		_ = os.RemoveAll(dataDir)
 3 	}()
 4 	suite := setupTest(dataDir, cfgSingleTenantTmpl)
 5-	s := GitSshServer(suite.cfg)
 6+	ctx, cancel := context.WithCancel(context.Background())
 7+	defer cancel()
 8+	s := GitSshServer(ctx, suite.cfg)
 9 	go func() {
10 		_ = s.ListenAndServe()
11 	}()
12@@ -48,8 +50,6 @@ func testSingleTenantE2E(t *testing.T) {
13 	actual, err := suite.userKey.Cmd(nil, "pr ls")
14 	bail(err)
15 	snaps.MatchSnapshot(t, actual)
16-
17-	_ = s.Shutdown(context.Background())
18 }
19 
20 func testMultiTenantE2E(t *testing.T) {
21@@ -59,7 +59,9 @@ func testMultiTenantE2E(t *testing.T) {
22 		_ = os.RemoveAll(dataDir)
23 	}()
24 	suite := setupTest(dataDir, cfgMultiTenantTmpl)
25-	s := GitSshServer(suite.cfg)
26+	ctx, cancel := context.WithCancel(context.Background())
27+	defer cancel()
28+	s := GitSshServer(ctx, suite.cfg)
29 	go func() {
30 		_ = s.ListenAndServe()
31 	}()
32@@ -135,8 +137,6 @@ func testMultiTenantE2E(t *testing.T) {
33 	actual, err = suite.userKey.Cmd(nil, "logs --repo admin/ai")
34 	bail(err)
35 	snaps.MatchSnapshot(t, actual)
36-
37-	_ = s.Shutdown(context.Background())
38 }
39 
40 type TestSuite struct {
M go.mod
+33, -49
  1@@ -1,87 +1,71 @@
  2 module github.com/picosh/git-pr
  3 
  4-go 1.22
  5+go 1.25
  6 
  7 require (
  8-	github.com/alecthomas/chroma/v2 v2.13.0
  9+	github.com/alecthomas/chroma/v2 v2.23.1
 10 	github.com/bluekeyes/go-gitdiff v0.8.0
 11 	github.com/charmbracelet/ssh v0.0.0-20240301204039-e79ff702f5b3
 12-	github.com/charmbracelet/wish v1.3.2
 13-	github.com/gkampitakis/go-snaps v0.5.7
 14-	github.com/gorilla/feeds v1.1.2
 15-	github.com/jmoiron/sqlx v1.3.5
 16+	github.com/gkampitakis/go-snaps v0.5.15
 17+	github.com/gorilla/feeds v1.2.0
 18+	github.com/jmoiron/sqlx v1.4.0
 19 	github.com/knadh/koanf/parsers/toml v0.1.0
 20 	github.com/knadh/koanf/providers/env v0.1.0
 21 	github.com/knadh/koanf/providers/file v1.0.0
 22 	github.com/knadh/koanf/v2 v2.1.1
 23 	github.com/oddg/hungarian-algorithm v0.0.0-20170809162819-9567cbc363de
 24+	github.com/picosh/pico v1.13.2-0.20260225015203-1f6cc61d65a3
 25 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 26 	github.com/urfave/cli/v2 v2.27.2
 27-	golang.org/x/crypto v0.21.0
 28-	modernc.org/sqlite v1.27.0
 29+	golang.org/x/crypto v0.47.0
 30+	modernc.org/sqlite v1.44.3
 31 )
 32 
 33 require (
 34 	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
 35-	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 36-	github.com/charmbracelet/bubbletea v0.25.0 // indirect
 37-	github.com/charmbracelet/keygen v0.5.0 // indirect
 38-	github.com/charmbracelet/lipgloss v0.10.0 // indirect
 39-	github.com/charmbracelet/log v0.3.1 // indirect
 40+	github.com/antoniomika/syncmap v1.0.0 // indirect
 41+	github.com/beorn7/perks v1.0.1 // indirect
 42+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 43 	github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 // indirect
 44 	github.com/charmbracelet/x/exp/term v0.0.0-20240229115032-4b79243a3516 // indirect
 45-	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
 46-	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
 47+	github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
 48 	github.com/creack/pty v1.1.21 // indirect
 49-	github.com/dlclark/regexp2 v1.11.0 // indirect
 50+	github.com/dlclark/regexp2 v1.11.5 // indirect
 51 	github.com/dustin/go-humanize v1.0.1 // indirect
 52 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 53-	github.com/gkampitakis/ciinfo v0.3.0 // indirect
 54+	github.com/gkampitakis/ciinfo v0.3.2 // indirect
 55 	github.com/gkampitakis/go-diff v1.3.2 // indirect
 56-	github.com/go-logfmt/logfmt v0.6.0 // indirect
 57+	github.com/go-andiamo/splitter v1.2.5 // indirect
 58 	github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
 59-	github.com/google/uuid v1.4.0 // indirect
 60-	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 61+	github.com/goccy/go-yaml v1.18.0 // indirect
 62+	github.com/google/uuid v1.6.0 // indirect
 63 	github.com/knadh/koanf/maps v0.1.1 // indirect
 64 	github.com/kr/pretty v0.3.1 // indirect
 65 	github.com/kr/text v0.2.0 // indirect
 66-	github.com/lib/pq v1.10.9 // indirect
 67-	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
 68 	github.com/maruel/natural v1.1.1 // indirect
 69 	github.com/mattn/go-isatty v0.0.20 // indirect
 70-	github.com/mattn/go-localereader v0.0.1 // indirect
 71-	github.com/mattn/go-runewidth v0.0.15 // indirect
 72 	github.com/mitchellh/copystructure v1.2.0 // indirect
 73 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 74-	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
 75-	github.com/muesli/cancelreader v0.2.2 // indirect
 76-	github.com/muesli/reflow v0.3.0 // indirect
 77-	github.com/muesli/termenv v0.15.2 // indirect
 78+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 79+	github.com/ncruces/go-strftime v1.0.0 // indirect
 80 	github.com/pelletier/go-toml v1.9.5 // indirect
 81-	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 82+	github.com/prometheus/client_golang v1.23.2 // indirect
 83+	github.com/prometheus/client_model v0.6.2 // indirect
 84+	github.com/prometheus/common v0.67.5 // indirect
 85+	github.com/prometheus/procfs v0.19.2 // indirect
 86 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 87-	github.com/rivo/uniseg v0.4.7 // indirect
 88-	github.com/rogpeppe/go-internal v1.12.0 // indirect
 89+	github.com/rogpeppe/go-internal v1.14.1 // indirect
 90 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 91-	github.com/tidwall/gjson v1.17.0 // indirect
 92-	github.com/tidwall/match v1.1.1 // indirect
 93+	github.com/tidwall/gjson v1.18.0 // indirect
 94+	github.com/tidwall/match v1.2.0 // indirect
 95 	github.com/tidwall/pretty v1.2.1 // indirect
 96 	github.com/tidwall/sjson v1.2.5 // indirect
 97 	github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
 98-	golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
 99-	golang.org/x/mod v0.14.0 // indirect
100-	golang.org/x/sync v0.6.0 // indirect
101-	golang.org/x/sys v0.21.0 // indirect
102-	golang.org/x/term v0.18.0 // indirect
103-	golang.org/x/text v0.14.0 // indirect
104-	golang.org/x/tools v0.15.0 // indirect
105-	lukechampine.com/uint128 v1.2.0 // indirect
106-	modernc.org/cc/v3 v3.40.0 // indirect
107-	modernc.org/ccgo/v3 v3.16.13 // indirect
108-	modernc.org/libc v1.29.0 // indirect
109-	modernc.org/mathutil v1.6.0 // indirect
110-	modernc.org/memory v1.7.2 // indirect
111-	modernc.org/opt v0.1.3 // indirect
112-	modernc.org/strutil v1.1.3 // indirect
113-	modernc.org/token v1.0.1 // indirect
114+	go.yaml.in/yaml/v2 v2.4.3 // indirect
115+	golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
116+	golang.org/x/sys v0.40.0 // indirect
117+	google.golang.org/protobuf v1.36.11 // indirect
118+	modernc.org/libc v1.67.7 // indirect
119+	modernc.org/mathutil v1.7.1 // indirect
120+	modernc.org/memory v1.11.0 // indirect
121 )
M go.sum
+185, -119
  1@@ -1,73 +1,104 @@
  2-github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
  3-github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
  4-github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
  5-github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
  6-github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
  7-github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
  8+codeberg.org/emersion/go-scfg v0.1.0 h1:6dnGU0ZI4gX+O5rMjwhoaySItzHG710eXL5TIQKl+uM=
  9+codeberg.org/emersion/go-scfg v0.1.0/go.mod h1:0nooW1ufBB4SlJEdTtiVN9Or+bnNM1icOkQ6Tbrq6O0=
 10+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 11+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 12+git.sr.ht/~delthas/senpai v0.4.1 h1:5CABpwJVzIBQaXtOngxG9TQDcxlaUqmGsXxlW+XcLuM=
 13+git.sr.ht/~delthas/senpai v0.4.1/go.mod h1:mzdu4o3wANA6cYzRnrz3w+uGPHA2z3j02JDrr/M3Myc=
 14+git.sr.ht/~rockorager/vaxis v0.15.1-0.20251218121515-cdf898cf10c7 h1:Hik9uYRclWV+mt5PnGc41aeGX6sh8MGzWcBnJt9EzpY=
 15+git.sr.ht/~rockorager/vaxis v0.15.1-0.20251218121515-cdf898cf10c7/go.mod h1:ptCAzb19xAhqhfAmCsN9Xnxdx01vlXkwZcuMYPnyqzE=
 16+github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 17+github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 18+github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
 19+github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
 20+github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
 21+github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 22 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 23 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 24-github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
 25-github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
 26+github.com/antoniomika/syncmap v1.0.0 h1:iFSfbQFQOvHZILFZF+hqWosO0no+W9+uF4y2VEyMKWU=
 27+github.com/antoniomika/syncmap v1.0.0/go.mod h1:fK2829foEYnO4riNfyUn0SHQZt4ue3DStYjGU+sJj38=
 28+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
 29+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
 30+github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 31+github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 32+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 33+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 34 github.com/bluekeyes/go-gitdiff v0.8.0 h1:Nn1wfw3/XeKoc3lWk+2bEXGUHIx36kj80FM1gVcBk+o=
 35 github.com/bluekeyes/go-gitdiff v0.8.0/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE=
 36-github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
 37-github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
 38-github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc=
 39-github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8=
 40-github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
 41-github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
 42-github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw=
 43-github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g=
 44+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 45+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 46 github.com/charmbracelet/ssh v0.0.0-20240301204039-e79ff702f5b3 h1:BI6Vno579jK/NKUwrvboHtMfF2On6kh6mU1cguf5+vQ=
 47 github.com/charmbracelet/ssh v0.0.0-20240301204039-e79ff702f5b3/go.mod h1:wUZ0VTrLI5ixIbYOSRHrqrZnfj8EXgLZOOvQYAQ2f18=
 48-github.com/charmbracelet/wish v1.3.2 h1:9+32OZnfebIw59Mcx0Yhsj6uke727bJVGJb6WolxsxQ=
 49-github.com/charmbracelet/wish v1.3.2/go.mod h1:aulqcv2nEoW14yC3tlkrmIbVN7qDjeH+pzIO239VGTA=
 50 github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 h1:3RXpZWGWTOeVXCTv0Dnzxdv/MhNUkBfEcbaTY0zrTQI=
 51 github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
 52 github.com/charmbracelet/x/exp/term v0.0.0-20240229115032-4b79243a3516 h1:wL/PiybPudKHv/LDgAUqS9eoPQr3pOAmzShMPG99cXA=
 53 github.com/charmbracelet/x/exp/term v0.0.0-20240229115032-4b79243a3516/go.mod h1:ntNL6rRIDmBHKUmo6ZKt344wCTcrPsSrfVj72qT8A5U=
 54-github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
 55-github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
 56-github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
 57-github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 58+github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
 59+github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
 60+github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
 61+github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
 62+github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
 63+github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
 64+github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
 65+github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 66 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 67 github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
 68 github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 69 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 70-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 71 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 72-github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
 73-github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 74+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 75+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 76+github.com/delthas/go-libnp v0.2.0 h1:OVll7Z9CER0rYpgiglXN1QvuNtkyY5X9uzLqm+xDstA=
 77+github.com/delthas/go-libnp v0.2.0/go.mod h1:LHUapIgbv1vBcZJU4cJRPkyslKhKe7GNRF38yUbl9QA=
 78+github.com/delthas/go-localeinfo v0.2.0 h1:0K5F5GQ5p4ealZjuBs4eubHLOIL21/YWSxs557hHZ+M=
 79+github.com/delthas/go-localeinfo v0.2.0/go.mod h1:sG54BxlyQgIskYURLrg7mvhoGBe0Qq12DNtYRALwNa4=
 80+github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
 81+github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
 82+github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
 83+github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 84 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 85 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 86+github.com/forPelevin/gomoji v1.4.1 h1:7U+Bl8o6RV/dOQz7coQFWj/jX6Ram6/cWFOuFDEPEUo=
 87+github.com/forPelevin/gomoji v1.4.1/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ=
 88 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 89 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 90-github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvpAv8=
 91-github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
 92+github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
 93+github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
 94 github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
 95 github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
 96-github.com/gkampitakis/go-snaps v0.5.7 h1:uVGjHR4t4pPHU944udMx7VKHpwepZXmvDMF+yDmI0rg=
 97-github.com/gkampitakis/go-snaps v0.5.7/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
 98-github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
 99-github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
100-github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
101-github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
102+github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
103+github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
104+github.com/go-andiamo/splitter v1.2.5 h1:P3NovWMY2V14TJJSolXBvlOmGSZo3Uz+LtTl2bsV/eY=
105+github.com/go-andiamo/splitter v1.2.5/go.mod h1:8WHU24t9hcMKU5FXDQb1hysSEC/GPuivIp0uKY1J8gw=
106+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
107+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
108+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
109+github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
110+github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
111 github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
112 github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
113-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
114-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
115-github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
116-github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
117-github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
118-github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
119-github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
120-github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
121+github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
122+github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
123+github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
124+github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
125+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
126+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
127+github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef h1:xpF9fUHpoIrrjX24DURVKiwHcFpw19ndIs+FwTSMbno=
128+github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
129+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
130+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
131+github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
132+github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
133+github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
134+github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
135+github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
136+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
137+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
138 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
139 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
140-github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
141-github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
142-github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
143-github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
144+github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
145+github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
146+github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
147+github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
148 github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
149 github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
150 github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
151@@ -85,65 +116,74 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
152 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
153 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
154 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
155-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
156-github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
157+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
158+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
159 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
160-github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
161-github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
162+github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI=
163+github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
164 github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
165 github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
166 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
167 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
168-github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
169-github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
170-github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
171-github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
172-github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
173-github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
174-github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
175-github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
176+github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
177+github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
178+github.com/mattn/go-sixel v0.0.8 h1:H0bBGQVOJoSvzvtTgCInxvg1IZiNlTcIIIx8A6uvjpQ=
179+github.com/mattn/go-sixel v0.0.8/go.mod h1:wbDSbrwpykVI1qEHyjZYsDgaJTwpVg9wSwmmh2slnBw=
180+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
181+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
182+github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
183+github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
184 github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
185 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
186 github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
187 github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
188-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
189-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
190-github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
191-github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
192-github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
193-github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
194-github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
195-github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
196+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
197+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
198+github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
199+github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
200 github.com/oddg/hungarian-algorithm v0.0.0-20170809162819-9567cbc363de h1:kuqx+ZOU3HjRVyMuT43K4xzTCqq+Ag1TCf8wtbYmqrw=
201 github.com/oddg/hungarian-algorithm v0.0.0-20170809162819-9567cbc363de/go.mod h1:dv3Q0yoeN8DwXGhZiv8Vi6/rr9mPtf4ylV60eLTGjUo=
202 github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
203 github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
204+github.com/picosh/pico v1.13.2-0.20260225015203-1f6cc61d65a3 h1:L315467SH4O1IlTmd/pjjvsD7IX9vnsY4pQw9WKnf1M=
205+github.com/picosh/pico v1.13.2-0.20260225015203-1f6cc61d65a3/go.mod h1:1Xml9JbPlipITzar/eu8/kcrfgXqXvx/XxL/xBj0xE8=
206+github.com/picosh/utils v0.0.0-20260125160622-5c3a9e231ec6 h1:9KfCtfcx7vrSyGU1K9whdE1crll9Aq+nAZ6c0FzuzvE=
207+github.com/picosh/utils v0.0.0-20260125160622-5c3a9e231ec6/go.mod h1:HogYEyJ43IGXrOa3D/kjM1pkzNAyh+pejRyv8Eo//pk=
208 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
209 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
210 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
211 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
212+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
213+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
214+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
215+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
216+github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
217+github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
218+github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
219+github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
220 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
221 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
222-github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
223-github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
224 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
225 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
226 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
227-github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
228-github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
229+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
230+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
231 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
232 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
233 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
234 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
235+github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
236+github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
237 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
238 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
239-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
240-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
241+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
242+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
243 github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
244-github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
245-github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
246-github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
247+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
248+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
249 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
250+github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
251+github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
252 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
253 github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
254 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
255@@ -153,55 +193,81 @@ github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
256 github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
257 github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
258 github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
259-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
260-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
261-golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
262-golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
263-golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
264-golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
265-golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
266-golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
267-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
268+github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
269+github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
270+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
271+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
272+github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
273+github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
274+go.abhg.dev/goldmark/anchor v0.2.0 h1:RQZTodRc6VHSUoQYKFlyH0pokbhk1klwUuGgDmjGp2E=
275+go.abhg.dev/goldmark/anchor v0.2.0/go.mod h1:Ym74zBV+QBKxK9ITOty680N9FT8otgGYvtYXroJUWms=
276+go.abhg.dev/goldmark/hashtag v0.4.0 h1:jkYcsIRUpog/rhWMnIOjj14qj8p5p4um+IzPbPUxFww=
277+go.abhg.dev/goldmark/hashtag v0.4.0/go.mod h1:5DQwSo2dEHKtsfXOC1+Po7WgVwR5IoGjgeVIerddz9g=
278+go.abhg.dev/goldmark/toc v0.12.0 h1:kiEBBIOB7jEzNpXmGdiL2L/zGSELKw/p3mosm2+RSuo=
279+go.abhg.dev/goldmark/toc v0.12.0/go.mod h1:kskbM5l9y8wOFEFfyEe9wnwhWeykvmHB6xEPCVrZIvg=
280+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
281+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
282+go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
283+go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
284+golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
285+golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
286+golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
287+golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
288+golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
289+golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
290+golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
291+golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
292+golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
293+golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
294+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
295+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
296 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
297-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
298-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
299-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
300-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
301-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
302-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
303-golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
304-golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
305+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
306+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
307+golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
308+golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
309+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
310+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
311+golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
312+golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
313+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
314+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
315 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
316 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
317+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
318+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
319 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
320+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
321 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
322 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
323 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
324-lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
325-lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
326-modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
327-modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
328-modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
329-modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
330-modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
331-modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
332-modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
333-modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
334-modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
335-modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
336-modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
337-modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
338-modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
339-modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
340-modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
341-modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
342-modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
343-modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
344-modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
345-modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
346-modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
347-modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
348-modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
349-modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
350-modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
351-modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
352+modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
353+modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
354+modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
355+modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
356+modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
357+modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
358+modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
359+modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
360+modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
361+modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
362+modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
363+modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
364+modernc.org/libc v1.67.7 h1:H+gYQw2PyidyxwxQsGTwQw6+6H+xUk+plvOKW7+d3TI=
365+modernc.org/libc v1.67.7/go.mod h1:UjCSJFl2sYbJbReVQeVpq/MgzlbmDM4cRHIYFelnaDk=
366+modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
367+modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
368+modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
369+modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
370+modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
371+modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
372+modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
373+modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
374+modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
375+modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
376+modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
377+modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
378+modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
379+modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
380+mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
381+mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
M mdw.go
+10, -8
 1@@ -3,23 +3,25 @@ package git
 2 import (
 3 	"fmt"
 4 
 5-	"github.com/charmbracelet/ssh"
 6-	"github.com/charmbracelet/wish"
 7+	"github.com/picosh/pico/pkg/pssh"
 8 )
 9 
10-func GitPatchRequestMiddleware(be *Backend, pr GitPatchRequest) wish.Middleware {
11-	return func(next ssh.Handler) ssh.Handler {
12-		return func(sesh ssh.Session) {
13+func GitPatchRequestMiddleware(be *Backend, pr GitPatchRequest) pssh.SSHServerMiddleware {
14+	return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
15+		return func(sesh *pssh.SSHServerConnSession) error {
16 			args := sesh.Command()
17 			cli := NewCli(sesh, be, pr)
18 			margs := append([]string{"git"}, args...)
19+			be.Logger.Info("ssh args", "args", args)
20 			err := cli.Run(margs)
21 			if err != nil {
22 				be.Logger.Error("error when running cli", "err", err)
23-				wish.Fatalln(sesh, fmt.Errorf("err: %w", err))
24-				next(sesh)
25-				return
26+				sesh.Fatal(fmt.Errorf("err: %w", err))
27+				_ = next(sesh)
28+				return err
29 			}
30+
31+			return nil
32 		}
33 	}
34 }
M ssh.go
+34, -21
 1@@ -1,17 +1,24 @@
 2 package git
 3 
 4 import (
 5+	"context"
 6 	"fmt"
 7+	"os"
 8 	"path/filepath"
 9 
10-	"github.com/charmbracelet/ssh"
11-	"github.com/charmbracelet/wish"
12+	"github.com/picosh/pico/pkg/pssh"
13+	"golang.org/x/crypto/ssh"
14 )
15 
16-func authHandler(pr *PrCmd) func(ctx ssh.Context, key ssh.PublicKey) bool {
17-	return func(ctx ssh.Context, key ssh.PublicKey) bool {
18+func authHandler(pr *PrCmd) func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
19+	return func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
20 		pubkey := pr.Backend.Pubkey(key)
21-		userName := ctx.User()
22+		userName := conn.User()
23+		perms := &ssh.Permissions{
24+			Extensions: map[string]string{
25+				"pubkey": pubkey,
26+			},
27+		}
28 		err := pr.IsBanned(pubkey, userName)
29 		if err != nil {
30 			pr.Backend.Logger.Info(
31@@ -20,13 +27,13 @@ func authHandler(pr *PrCmd) func(ctx ssh.Context, key ssh.PublicKey) bool {
32 				"username", userName,
33 				"pubkey", pubkey,
34 			)
35-			return false
36+			return perms, err
37 		}
38-		return true
39+		return perms, nil
40 	}
41 }
42 
43-func GitSshServer(cfg *GitCfg) *ssh.Server {
44+func GitSshServer(ctx context.Context, cfg *GitCfg) *pssh.SSHServer {
45 	dbpath := filepath.Join(cfg.DataDir, "pr.db?_fk=on")
46 	dbh, err := SqliteOpen("file:"+dbpath, cfg.Logger)
47 	if err != nil {
48@@ -38,25 +45,31 @@ func GitSshServer(cfg *GitCfg) *ssh.Server {
49 		Logger: cfg.Logger,
50 		Cfg:    cfg,
51 	}
52+
53 	prCmd := &PrCmd{
54 		Backend: be,
55 	}
56 
57-	s, err := wish.NewServer(
58-		wish.WithAddress(
59-			fmt.Sprintf("%s:%s", cfg.Host, cfg.SshPort),
60-		),
61-		wish.WithHostKeyPath(
62-			filepath.Join(cfg.DataDir, "term_info_ed25519"),
63-		),
64-		wish.WithPublicKeyAuth(authHandler(prCmd)),
65-		wish.WithMiddleware(
66+	server, err := pssh.NewSSHServerWithConfig(
67+		ctx,
68+		cfg.Logger,
69+		"git-pr",
70+		cfg.Host,
71+		cfg.SshPort,
72+		cfg.PromPort,
73+		filepath.Join(cfg.DataDir, "term_info_ed25519"),
74+		authHandler(prCmd),
75+		[]pssh.SSHServerMiddleware{
76 			GitPatchRequestMiddleware(be, prCmd),
77-		),
78+		},
79+		[]pssh.SSHServerMiddleware{},
80+		nil,
81 	)
82+
83 	if err != nil {
84-		cfg.Logger.Error("could not create server", "err", err)
85-		return nil
86+		cfg.Logger.Error("failed to create ssh server", "err", err)
87+		os.Exit(1)
88 	}
89-	return s
90+
91+	return server
92 }