detached b25a2bc

feat: custom models with baseUrl + domain migration to www.zot.sh

Adds baseUrl support in models.json for local models (ollama, vLLM, etc). Migrates all install URLs and references from zot.patriceckhart.com to www.zot.sh.
commit
b25a2bc8543f9d8d602168d6f0e97792345bfc11
author
patriceckhart · · 2026-04-24 12:00 UTC
parent
dcc9ba0
signature
unsigned
refs
tag: v0.1.6
5 files · +33 -7
M README.md +3 -3
@@ -18,19 +18,19 @@ Yet another coding agent harness, lightweight and written (vibe-slopped) in go.
1818 ### One-liner (macOS, Linux)
1919
2020 ```bash
21-curl -fsSL https://zot.patriceckhart.com/install.sh | bash
21+curl -fsSL https://www.zot.sh/install.sh | bash
2222 ```
2323
2424 Detects your OS and architecture, downloads the latest release from GitHub, verifies the SHA-256 against the release's `checksums.txt`, extracts the binary, and drops it in `/usr/local/bin`, `~/.local/bin`, or `~/bin`, whichever is writable first. Pass a version or prefix to pin:
2525
2626 ```bash
27-curl -fsSL https://zot.patriceckhart.com/install.sh | bash -s -- v0.0.1 ~/bin
27+curl -fsSL https://www.zot.sh/install.sh | bash -s -- v0.0.1 ~/bin
2828 ```
2929
3030 ### One-liner (Windows, PowerShell)
3131
3232 ```powershell
33-iwr -useb https://zot.patriceckhart.com/install.ps1 | iex
33+iwr -useb https://www.zot.sh/install.ps1 | iex
3434 ```
3535
3636 Drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards.
@@ -18,19 +18,19 @@ Yet another coding agent harness, lightweight and written (vibe-slopped) in go.
18 ### One-liner (macOS, Linux)18 ### One-liner (macOS, Linux)
19 19
20 ```bash20 ```bash
21-curl -fsSL https://zot.patriceckhart.com/install.sh | bash21+curl -fsSL https://www.zot.sh/install.sh | bash
22 ```22 ```
23 23
24 Detects your OS and architecture, downloads the latest release from GitHub, verifies the SHA-256 against the release's `checksums.txt`, extracts the binary, and drops it in `/usr/local/bin`, `~/.local/bin`, or `~/bin`, whichever is writable first. Pass a version or prefix to pin:24 Detects your OS and architecture, downloads the latest release from GitHub, verifies the SHA-256 against the release's `checksums.txt`, extracts the binary, and drops it in `/usr/local/bin`, `~/.local/bin`, or `~/bin`, whichever is writable first. Pass a version or prefix to pin:
25 25
26 ```bash26 ```bash
27-curl -fsSL https://zot.patriceckhart.com/install.sh | bash -s -- v0.0.1 ~/bin27+curl -fsSL https://www.zot.sh/install.sh | bash -s -- v0.0.1 ~/bin
28 ```28 ```
29 29
30 ### One-liner (Windows, PowerShell)30 ### One-liner (Windows, PowerShell)
31 31
32 ```powershell32 ```powershell
33-iwr -useb https://zot.patriceckhart.com/install.ps1 | iex33+iwr -useb https://www.zot.sh/install.ps1 | iex
34 ```34 ```
35 35
36 Drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards.36 Drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards.
M internal/agent/build.go +16 -1
@@ -167,10 +167,25 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
167167 model = provider.DefaultModel.ID
168168 }
169169 }
170- if _, err := provider.FindModel(provName, model); err != nil {
170+ resolvedModel, err := provider.FindModel(provName, model)
171+ if err != nil {
171172 return Resolved{}, err
172173 }
173174
175+ // If the model defines a base URL (e.g. local ollama) and the
176+ // user didn't pass --base-url, use the model's URL.
177+ if args.BaseURL == "" && resolvedModel.BaseURL != "" {
178+ args.BaseURL = resolvedModel.BaseURL
179+ }
180+
181+ // If the model has a base URL, credentials are optional (local
182+ // models like ollama don't need real API keys).
183+ if resolvedModel.BaseURL != "" && credErr != nil {
184+ cred = "ollama"
185+ credErr = nil
186+ requireCred = false
187+ }
188+
174189 if credErr != nil && requireCred {
175190 return Resolved{}, fmt.Errorf("%w; set %s_API_KEY, pass --api-key, or run `zot` and /login",
176191 credErr, envVarName(provName))
@@ -167,10 +167,25 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
167 model = provider.DefaultModel.ID167 model = provider.DefaultModel.ID
168 }168 }
169 }169 }
170- if _, err := provider.FindModel(provName, model); err != nil {170+ resolvedModel, err := provider.FindModel(provName, model)
171+ if err != nil {
171 return Resolved{}, err172 return Resolved{}, err
172 }173 }
173 174
175+ // If the model defines a base URL (e.g. local ollama) and the
176+ // user didn't pass --base-url, use the model's URL.
177+ if args.BaseURL == "" && resolvedModel.BaseURL != "" {
178+ args.BaseURL = resolvedModel.BaseURL
179+ }
180+
181+ // If the model has a base URL, credentials are optional (local
182+ // models like ollama don't need real API keys).
183+ if resolvedModel.BaseURL != "" && credErr != nil {
184+ cred = "ollama"
185+ credErr = nil
186+ requireCred = false
187+ }
188+
174 if credErr != nil && requireCred {189 if credErr != nil && requireCred {
175 return Resolved{}, fmt.Errorf("%w; set %s_API_KEY, pass --api-key, or run `zot` and /login",190 return Resolved{}, fmt.Errorf("%w; set %s_API_KEY, pass --api-key, or run `zot` and /login",
176 credErr, envVarName(provName))191 credErr, envVarName(provName))
M internal/agent/modes/update_banner.go +3 -3
@@ -48,12 +48,12 @@ func renderUpdateBanner(th tui.Theme, info UpdateInfo, width int) []string {
4848 }
4949
5050 // recommendedUpdateCommand returns the one-liner appropriate for the
51-// user's platform. Points at zot.patriceckhart.com, which 301s to
51+// user's platform. Points at www.zot.sh, which 301s to
5252 // the raw install scripts on github — same surface, stable short
5353 // URL that's safe to hard-code even if the scripts later move.
5454 func recommendedUpdateCommand() string {
5555 if runtime.GOOS == "windows" {
56- return `iwr -useb https://zot.patriceckhart.com/install.ps1 | iex`
56+ return `iwr -useb https://www.zot.sh/install.ps1 | iex`
5757 }
58- return `curl -fsSL https://zot.patriceckhart.com/install.sh | bash`
58+ return `curl -fsSL https://www.zot.sh/install.sh | bash`
5959 }
@@ -48,12 +48,12 @@ func renderUpdateBanner(th tui.Theme, info UpdateInfo, width int) []string {
48 }48 }
49 49
50 // recommendedUpdateCommand returns the one-liner appropriate for the50 // recommendedUpdateCommand returns the one-liner appropriate for the
51-// user's platform. Points at zot.patriceckhart.com, which 301s to51+// user's platform. Points at www.zot.sh, which 301s to
52 // the raw install scripts on github — same surface, stable short52 // the raw install scripts on github — same surface, stable short
53 // URL that's safe to hard-code even if the scripts later move.53 // URL that's safe to hard-code even if the scripts later move.
54 func recommendedUpdateCommand() string {54 func recommendedUpdateCommand() string {
55 if runtime.GOOS == "windows" {55 if runtime.GOOS == "windows" {
56- return `iwr -useb https://zot.patriceckhart.com/install.ps1 | iex`56+ return `iwr -useb https://www.zot.sh/install.ps1 | iex`
57 }57 }
58- return `curl -fsSL https://zot.patriceckhart.com/install.sh | bash`58+ return `curl -fsSL https://www.zot.sh/install.sh | bash`
59 }59 }
M internal/provider/models.go +6 -0
@@ -25,6 +25,12 @@ type Model struct {
2525 // today but start working the moment the provider flips the switch.
2626 Speculative bool
2727
28+ // BaseURL overrides the provider's default API endpoint for this
29+ // model. Optional; when empty the provider's default (or the
30+ // --base-url flag) is used. Useful for local models served by
31+ // ollama, vLLM, LM Studio, etc.
32+ BaseURL string
33+
2834 // Source is where this model entry came from: "catalog" (baked in),
2935 // "live" (discovered via /v1/models), or "cache" (loaded from the
3036 // on-disk cache). Informational.
@@ -25,6 +25,12 @@ type Model struct {
25 // today but start working the moment the provider flips the switch.25 // today but start working the moment the provider flips the switch.
26 Speculative bool26 Speculative bool
27 27
28+ // BaseURL overrides the provider's default API endpoint for this
29+ // model. Optional; when empty the provider's default (or the
30+ // --base-url flag) is used. Useful for local models served by
31+ // ollama, vLLM, LM Studio, etc.
32+ BaseURL string
33+
28 // Source is where this model entry came from: "catalog" (baked in),34 // Source is where this model entry came from: "catalog" (baked in),
29 // "live" (discovered via /v1/models), or "cache" (loaded from the35 // "live" (discovered via /v1/models), or "cache" (loaded from the
30 // on-disk cache). Informational.36 // on-disk cache). Informational.
M internal/provider/usermodels.go +5 -0
@@ -49,6 +49,7 @@ type UserModel struct {
4949 PriceOutput float64 `json:"priceOutput"`
5050 PriceCacheRead float64 `json:"priceCacheRead"`
5151 PriceCacheWrite float64 `json:"priceCacheWrite"`
52+ BaseURL string `json:"baseUrl,omitempty"`
5253 Input []string `json:"input"` // informational only
5354 API string `json:"api"` // informational only
5455 }
@@ -91,6 +92,7 @@ func LoadUserModels(path string) []Model {
9192 PriceOutput: um.PriceOutput,
9293 PriceCacheRead: um.PriceCacheRead,
9394 PriceCacheWrite: um.PriceCacheWrite,
95+ BaseURL: um.BaseURL,
9496 Source: "user",
9597 }
9698 if m.DisplayName == "" {
@@ -147,6 +149,9 @@ func SetUserModels(models []Model) {
147149 existing.MaxOutput = um.MaxOutput
148150 }
149151 existing.Reasoning = um.Reasoning
152+ if um.BaseURL != "" {
153+ existing.BaseURL = um.BaseURL
154+ }
150155 existing.Source = "user"
151156 existing.Speculative = false
152157 active[idx] = existing
@@ -49,6 +49,7 @@ type UserModel struct {
49 PriceOutput float64 `json:"priceOutput"`49 PriceOutput float64 `json:"priceOutput"`
50 PriceCacheRead float64 `json:"priceCacheRead"`50 PriceCacheRead float64 `json:"priceCacheRead"`
51 PriceCacheWrite float64 `json:"priceCacheWrite"`51 PriceCacheWrite float64 `json:"priceCacheWrite"`
52+ BaseURL string `json:"baseUrl,omitempty"`
52 Input []string `json:"input"` // informational only53 Input []string `json:"input"` // informational only
53 API string `json:"api"` // informational only54 API string `json:"api"` // informational only
54 }55 }
@@ -91,6 +92,7 @@ func LoadUserModels(path string) []Model {
91 PriceOutput: um.PriceOutput,92 PriceOutput: um.PriceOutput,
92 PriceCacheRead: um.PriceCacheRead,93 PriceCacheRead: um.PriceCacheRead,
93 PriceCacheWrite: um.PriceCacheWrite,94 PriceCacheWrite: um.PriceCacheWrite,
95+ BaseURL: um.BaseURL,
94 Source: "user",96 Source: "user",
95 }97 }
96 if m.DisplayName == "" {98 if m.DisplayName == "" {
@@ -147,6 +149,9 @@ func SetUserModels(models []Model) {
147 existing.MaxOutput = um.MaxOutput149 existing.MaxOutput = um.MaxOutput
148 }150 }
149 existing.Reasoning = um.Reasoning151 existing.Reasoning = um.Reasoning
152+ if um.BaseURL != "" {
153+ existing.BaseURL = um.BaseURL
154+ }
150 existing.Source = "user"155 existing.Source = "user"
151 existing.Speculative = false156 existing.Speculative = false
152 active[idx] = existing157 active[idx] = existing

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close