detached 482a51c

Merge branch 'main' into fix/editproperly

commit
482a51c3ebee2e718dac0e5e6b11530b52dbac9d
author
Patric Eckhart · · 2026-07-12 08:01 UTC
committer
GitHub · · 2026-07-12 08:01 UTC
merge of
4463630 + 525ca7f · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
8 files · +52 -19
M README.md +11 -4
@@ -3,6 +3,12 @@
33 <img src="packages/provider/auth/assets/zot-logo.png" alt="zot coding agent harness" width="130" height="130" />
44 </a>
55 </div>
6+<br>
7+<p align="center">
8+ <a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-blue"></a>
9+ <img alt="Go 1.25+" src="https://img.shields.io/badge/Go-1.25+-00ADD8?logo=go&logoColor=white">
10+ <img alt="30+ providers" src="https://img.shields.io/badge/providers-30+-34E2EA">
11+</p>
612 <p align="center">
713 <a href="https://www.zot.sh">zot.sh</a>
814 </p>
@@ -73,9 +79,10 @@ The easiest way is to just run `zot` and type `/login`. The TUI opens even witho
7379 3. `$ZOT_HOME/auth.json` (API key or OAuth token; mode 0600)
7480
7581 `$ZOT_HOME` defaults to:
76-- macOS: `~/Library/Application Support/zot`
77-- Linux: `$XDG_STATE_HOME/zot` or `~/.local/state/zot`
78-- Windows: `%LOCALAPPDATA%\zot`
82+- All platforms: `$XDG_STATE_HOME/zot` when `XDG_STATE_HOME` is set
83+- macOS fallback: `~/Library/Application Support/zot`
84+- Linux fallback: `~/.local/state/zot`
85+- Windows fallback: `%LOCALAPPDATA%\zot`
7986
8087 ### `/login` flow
8188
@@ -392,7 +399,7 @@ No configuration is required — the candidate list is built dynamically from yo
392399
393400 ### Custom models
394401
395-Place a `models.json` in `$ZOT_HOME` (macOS: `~/Library/Application Support/zot/`, Linux: `~/.local/state/zot/`) to add models that aren't in the baked-in catalog or to override existing entries:
402+Place a `models.json` in `$ZOT_HOME` (`$XDG_STATE_HOME/zot/` when set, otherwise the platform default above) to add models that aren't in the baked-in catalog or to override existing entries:
396403
397404 ```json
398405 {
@@ -3,6 +3,12 @@
3 <img src="packages/provider/auth/assets/zot-logo.png" alt="zot coding agent harness" width="130" height="130" />3 <img src="packages/provider/auth/assets/zot-logo.png" alt="zot coding agent harness" width="130" height="130" />
4 </a>4 </a>
5 </div>5 </div>
6+<br>
7+<p align="center">
8+ <a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-blue"></a>
9+ <img alt="Go 1.25+" src="https://img.shields.io/badge/Go-1.25+-00ADD8?logo=go&logoColor=white">
10+ <img alt="30+ providers" src="https://img.shields.io/badge/providers-30+-34E2EA">
11+</p>
6 <p align="center">12 <p align="center">
7 <a href="https://www.zot.sh">zot.sh</a>13 <a href="https://www.zot.sh">zot.sh</a>
8 </p>14 </p>
@@ -73,9 +79,10 @@ The easiest way is to just run `zot` and type `/login`. The TUI opens even witho
73 3. `$ZOT_HOME/auth.json` (API key or OAuth token; mode 0600)79 3. `$ZOT_HOME/auth.json` (API key or OAuth token; mode 0600)
74 80
75 `$ZOT_HOME` defaults to:81 `$ZOT_HOME` defaults to:
76-- macOS: `~/Library/Application Support/zot`82+- All platforms: `$XDG_STATE_HOME/zot` when `XDG_STATE_HOME` is set
77-- Linux: `$XDG_STATE_HOME/zot` or `~/.local/state/zot`83+- macOS fallback: `~/Library/Application Support/zot`
78-- Windows: `%LOCALAPPDATA%\zot`84+- Linux fallback: `~/.local/state/zot`
85+- Windows fallback: `%LOCALAPPDATA%\zot`
79 86
80 ### `/login` flow87 ### `/login` flow
81 88
@@ -392,7 +399,7 @@ No configuration is required — the candidate list is built dynamically from yo
392 399
393 ### Custom models400 ### Custom models
394 401
395-Place a `models.json` in `$ZOT_HOME` (macOS: `~/Library/Application Support/zot/`, Linux: `~/.local/state/zot/`) to add models that aren't in the baked-in catalog or to override existing entries:402+Place a `models.json` in `$ZOT_HOME` (`$XDG_STATE_HOME/zot/` when set, otherwise the platform default above) to add models that aren't in the baked-in catalog or to override existing entries:
396 403
397 ```json404 ```json
398 {405 {
M docs/extensions.md +3 -2
@@ -97,8 +97,9 @@ zot scans two directories on startup, in this order:
9797 2. **Global**: `$ZOT_HOME/extensions/<name>/extension.json`
9898
9999 A project-local extension with the same name wins over a global one.
100-On macOS `$ZOT_HOME` defaults to `~/Library/Application Support/zot/`;
101-on Linux it's `$XDG_STATE_HOME/zot` or `~/.local/state/zot`.
100+When `XDG_STATE_HOME` is set on any platform, `$ZOT_HOME` defaults to
101+`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`
102+on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.
102103
103104 Because each extension owns its own directory, the recommended place
104105 for extension state is inside that directory itself (for example
@@ -97,8 +97,9 @@ zot scans two directories on startup, in this order:
97 2. **Global**: `$ZOT_HOME/extensions/<name>/extension.json`97 2. **Global**: `$ZOT_HOME/extensions/<name>/extension.json`
98 98
99 A project-local extension with the same name wins over a global one.99 A project-local extension with the same name wins over a global one.
100-On macOS `$ZOT_HOME` defaults to `~/Library/Application Support/zot/`;100+When `XDG_STATE_HOME` is set on any platform, `$ZOT_HOME` defaults to
101-on Linux it's `$XDG_STATE_HOME/zot` or `~/.local/state/zot`.101+`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`
102+on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.
102 103
103 Because each extension owns its own directory, the recommended place104 Because each extension owns its own directory, the recommended place
104 for extension state is inside that directory itself (for example105 for extension state is inside that directory itself (for example
M docs/skills.md +3 -2
@@ -64,8 +64,9 @@ skill ecosystem works in zot unchanged. Drop your existing
6464 `.claude/skills/` or `.agents/skills/` directories into a project and
6565 zot will pick them up.
6666
67-`$ZOT_HOME` defaults to `~/Library/Application Support/zot/` on macOS,
68-`$XDG_STATE_HOME/zot` on Linux, `%LOCALAPPDATA%\zot` on Windows.
67+When `XDG_STATE_HOME` is set on any platform, `$ZOT_HOME` defaults to
68+`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`
69+on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.
6970
7071 ## Inspecting installed skills
7172
@@ -64,8 +64,9 @@ skill ecosystem works in zot unchanged. Drop your existing
64 `.claude/skills/` or `.agents/skills/` directories into a project and64 `.claude/skills/` or `.agents/skills/` directories into a project and
65 zot will pick them up.65 zot will pick them up.
66 66
67-`$ZOT_HOME` defaults to `~/Library/Application Support/zot/` on macOS,67+When `XDG_STATE_HOME` is set on any platform, `$ZOT_HOME` defaults to
68-`$XDG_STATE_HOME/zot` on Linux, `%LOCALAPPDATA%\zot` on Windows.68+`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`
69+on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.
69 70
70 ## Inspecting installed skills71 ## Inspecting installed skills
71 72
M examples/extensions/mcp-bridge/Makefile +3 -3
@@ -4,10 +4,10 @@ BIN := $(notdir $(CURDIR))
44 # Must match "name" in extension.json (slash command/logical extension name).
55 EXT := mcp
66 ZOT_HOME ?= $(shell \
7- if [ "$$(uname -s)" = "Darwin" ]; then \
8- printf '%s/Library/Application Support/zot' "$$HOME"; \
9- elif [ -n "$$XDG_STATE_HOME" ]; then \
7+ if [ -n "$$XDG_STATE_HOME" ]; then \
108 printf '%s/zot' "$$XDG_STATE_HOME"; \
9+ elif [ "$$(uname -s)" = "Darwin" ]; then \
10+ printf '%s/Library/Application Support/zot' "$$HOME"; \
1111 else \
1212 printf '%s/.local/state/zot' "$$HOME"; \
1313 fi)
@@ -4,10 +4,10 @@ BIN := $(notdir $(CURDIR))
4 # Must match "name" in extension.json (slash command/logical extension name).4 # Must match "name" in extension.json (slash command/logical extension name).
5 EXT := mcp5 EXT := mcp
6 ZOT_HOME ?= $(shell \6 ZOT_HOME ?= $(shell \
7- if [ "$$(uname -s)" = "Darwin" ]; then \7+ if [ -n "$$XDG_STATE_HOME" ]; then \
8- printf '%s/Library/Application Support/zot' "$$HOME"; \
9- elif [ -n "$$XDG_STATE_HOME" ]; then \
10 printf '%s/zot' "$$XDG_STATE_HOME"; \8 printf '%s/zot' "$$XDG_STATE_HOME"; \
9+ elif [ "$$(uname -s)" = "Darwin" ]; then \
10+ printf '%s/Library/Application Support/zot' "$$HOME"; \
11 else \11 else \
12 printf '%s/.local/state/zot' "$$HOME"; \12 printf '%s/.local/state/zot' "$$HOME"; \
13 fi)13 fi)
M examples/extensions/mcp-bridge/README.md +1 -1
@@ -61,7 +61,7 @@ Config files are loaded from two locations (project overrides global per-server)
6161
6262 | Location | Scope |
6363 |---|---|
64-| `$ZOT_HOME/mcp.json` | Global (macOS: `~/Library/Application Support/zot/mcp.json`) |
64+| `$ZOT_HOME/mcp.json` | Global (`$XDG_STATE_HOME/zot/mcp.json` when `XDG_STATE_HOME` is set) |
6565 | `.zot/mcp.json` | Project-level (in your project root) |
6666
6767 ### Config Format
@@ -61,7 +61,7 @@ Config files are loaded from two locations (project overrides global per-server)
61 61
62 | Location | Scope |62 | Location | Scope |
63 |---|---|63 |---|---|
64-| `$ZOT_HOME/mcp.json` | Global (macOS: `~/Library/Application Support/zot/mcp.json`) |64+| `$ZOT_HOME/mcp.json` | Global (`$XDG_STATE_HOME/zot/mcp.json` when `XDG_STATE_HOME` is set) |
65 | `.zot/mcp.json` | Project-level (in your project root) |65 | `.zot/mcp.json` | Project-level (in your project root) |
66 66
67 ### Config Format67 ### Config Format
M examples/extensions/mcp-bridge/config.go +4 -4
@@ -3,7 +3,7 @@
33 // Reads standard MCP config files (same format as Claude Desktop, Cursor, etc.)
44 // from two locations:
55 //
6-// 1. Global: $ZOT_HOME/mcp.json (macOS: ~/Library/Application Support/zot/mcp.json)
6+// 1. Global: $ZOT_HOME/mcp.json
77 // 2. Project: .zot/mcp.json (in the current working directory)
88 //
99 // Project config overrides global config per-server (shallow merge).
@@ -47,6 +47,9 @@ func zotHome() string {
4747 if h := os.Getenv("ZOT_HOME"); h != "" {
4848 return h
4949 }
50+ if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {
51+ return filepath.Join(xdg, "zot")
52+ }
5053 switch runtime.GOOS {
5154 case "darwin":
5255 home, _ := os.UserHomeDir()
@@ -54,9 +57,6 @@ func zotHome() string {
5457 case "windows":
5558 return filepath.Join(os.Getenv("APPDATA"), "zot")
5659 default: // linux, freebsd, etc.
57- if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {
58- return filepath.Join(xdg, "zot")
59- }
6060 home, _ := os.UserHomeDir()
6161 return filepath.Join(home, ".local", "state", "zot")
6262 }
@@ -3,7 +3,7 @@
3 // Reads standard MCP config files (same format as Claude Desktop, Cursor, etc.)3 // Reads standard MCP config files (same format as Claude Desktop, Cursor, etc.)
4 // from two locations:4 // from two locations:
5 //5 //
6-// 1. Global: $ZOT_HOME/mcp.json (macOS: ~/Library/Application Support/zot/mcp.json)6+// 1. Global: $ZOT_HOME/mcp.json
7 // 2. Project: .zot/mcp.json (in the current working directory)7 // 2. Project: .zot/mcp.json (in the current working directory)
8 //8 //
9 // Project config overrides global config per-server (shallow merge).9 // Project config overrides global config per-server (shallow merge).
@@ -47,6 +47,9 @@ func zotHome() string {
47 if h := os.Getenv("ZOT_HOME"); h != "" {47 if h := os.Getenv("ZOT_HOME"); h != "" {
48 return h48 return h
49 }49 }
50+ if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {
51+ return filepath.Join(xdg, "zot")
52+ }
50 switch runtime.GOOS {53 switch runtime.GOOS {
51 case "darwin":54 case "darwin":
52 home, _ := os.UserHomeDir()55 home, _ := os.UserHomeDir()
@@ -54,9 +57,6 @@ func zotHome() string {
54 case "windows":57 case "windows":
55 return filepath.Join(os.Getenv("APPDATA"), "zot")58 return filepath.Join(os.Getenv("APPDATA"), "zot")
56 default: // linux, freebsd, etc.59 default: // linux, freebsd, etc.
57- if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {
58- return filepath.Join(xdg, "zot")
59- }
60 home, _ := os.UserHomeDir()60 home, _ := os.UserHomeDir()
61 return filepath.Join(home, ".local", "state", "zot")61 return filepath.Join(home, ".local", "state", "zot")
62 }62 }
M packages/agent/config.go +3 -3
@@ -108,6 +108,9 @@ func ZotHome() string {
108108 if v := os.Getenv("ZOT_HOME"); v != "" {
109109 return v
110110 }
111+ if v := os.Getenv("XDG_STATE_HOME"); v != "" {
112+ return filepath.Join(v, "zot")
113+ }
111114 switch runtime.GOOS {
112115 case "darwin":
113116 if home, err := os.UserHomeDir(); err == nil {
@@ -118,9 +121,6 @@ func ZotHome() string {
118121 return filepath.Join(v, "zot")
119122 }
120123 }
121- if v := os.Getenv("XDG_STATE_HOME"); v != "" {
122- return filepath.Join(v, "zot")
123- }
124124 if home, err := os.UserHomeDir(); err == nil {
125125 return filepath.Join(home, ".local", "state", "zot")
126126 }
@@ -108,6 +108,9 @@ func ZotHome() string {
108 if v := os.Getenv("ZOT_HOME"); v != "" {108 if v := os.Getenv("ZOT_HOME"); v != "" {
109 return v109 return v
110 }110 }
111+ if v := os.Getenv("XDG_STATE_HOME"); v != "" {
112+ return filepath.Join(v, "zot")
113+ }
111 switch runtime.GOOS {114 switch runtime.GOOS {
112 case "darwin":115 case "darwin":
113 if home, err := os.UserHomeDir(); err == nil {116 if home, err := os.UserHomeDir(); err == nil {
@@ -118,9 +121,6 @@ func ZotHome() string {
118 return filepath.Join(v, "zot")121 return filepath.Join(v, "zot")
119 }122 }
120 }123 }
121- if v := os.Getenv("XDG_STATE_HOME"); v != "" {
122- return filepath.Join(v, "zot")
123- }
124 if home, err := os.UserHomeDir(); err == nil {124 if home, err := os.UserHomeDir(); err == nil {
125 return filepath.Join(home, ".local", "state", "zot")125 return filepath.Join(home, ".local", "state", "zot")
126 }126 }
A packages/agent/config_home_test.go +24 -0
@@ -0,0 +1,24 @@
1+package agent
2+
3+import (
4+ "path/filepath"
5+ "testing"
6+)
7+
8+func TestZotHomePrefersExplicitHomeOverXDGStateHome(t *testing.T) {
9+ t.Setenv("ZOT_HOME", filepath.Join("explicit", "zot"))
10+ t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))
11+
12+ if got, want := ZotHome(), filepath.Join("explicit", "zot"); got != want {
13+ t.Fatalf("ZotHome() = %q, want %q", got, want)
14+ }
15+}
16+
17+func TestZotHomeUsesXDGStateHome(t *testing.T) {
18+ t.Setenv("ZOT_HOME", "")
19+ t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))
20+
21+ if got, want := ZotHome(), filepath.Join("xdg", "state", "zot"); got != want {
22+ t.Fatalf("ZotHome() = %q, want %q", got, want)
23+ }
24+}
@@ -0,0 +1,24 @@
1+package agent
2+
3+import (
4+ "path/filepath"
5+ "testing"
6+)
7+
8+func TestZotHomePrefersExplicitHomeOverXDGStateHome(t *testing.T) {
9+ t.Setenv("ZOT_HOME", filepath.Join("explicit", "zot"))
10+ t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))
11+
12+ if got, want := ZotHome(), filepath.Join("explicit", "zot"); got != want {
13+ t.Fatalf("ZotHome() = %q, want %q", got, want)
14+ }
15+}
16+
17+func TestZotHomeUsesXDGStateHome(t *testing.T) {
18+ t.Setenv("ZOT_HOME", "")
19+ t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))
20+
21+ if got, want := ZotHome(), filepath.Join("xdg", "state", "zot"); got != want {
22+ t.Fatalf("ZotHome() = %q, want %q", got, want)
23+ }
24+}

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 477

↑↓ moveenter openesc close