detached 2bf38c4

Merge pull request #35 from bnema/rename/zut

feat!: rename zot to zut
commit
2bf38c4a724b894b7df14bfb81740cfec876332c
author
bnema · · 2026-08-05 18:21 UTC
committer
GitHub · · 2026-08-05 18:21 UTC
merge of
00ca863 + 2744213 · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
refs
tag: v0.1.0
322 files · +2559 -2384
M .github/ISSUE_TEMPLATE/bug_report.yml +2 -2
@@ -24,7 +24,7 @@ body:
2424 attributes:
2525 label: Expected behavior
2626 description: What should have happened instead?
27- placeholder: "I expected zot to..."
27+ placeholder: "I expected zut to..."
2828 validations:
2929 required: true
3030
@@ -32,7 +32,7 @@ body:
3232 id: version
3333 attributes:
3434 label: Version
35- description: "e.g. 0.0.1 — run `zot --version` to check"
35+ description: "e.g. 0.0.1 — run `zut --version` to check"
3636 validations:
3737 required: true
3838
M .github/ISSUE_TEMPLATE/feature_request.yml +2 -2
@@ -8,7 +8,7 @@ body:
88 value: |
99 Thanks for the idea! A short conversation here saves everyone time before any code is written.
1010
11- Zot is intentionally focused — not every idea will make it in. A "no" is not a judgment of the idea.
11+ Zut is intentionally focused — not every idea will make it in. A "no" is not a judgment of the idea.
1212
1313 - type: textarea
1414 id: problem
@@ -16,7 +16,7 @@ body:
1616 label: What problem does this solve?
1717 description: |
1818 The **why**, not the **what**. What were you trying to do when you wished this existed?
19- placeholder: "When I'm working with multiple providers, I want to switch between them without restarting zot."
19+ placeholder: "When I'm working with multiple providers, I want to switch between them without restarting zut."
2020 validations:
2121 required: true
2222
A .github/scripts/next-release-tag.sh +60 -0
@@ -0,0 +1,60 @@
1+#!/usr/bin/env bash
2+# Print the release tag for HEAD, or nothing when its unreleased commits do not
3+# require a release. A tag already pointing to HEAD is emitted again so a
4+# failed GoReleaser job can be retried without minting a second tag.
5+set -euo pipefail
6+
7+latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1)
8+head=$(git rev-parse HEAD)
9+if [ -n "$latest" ] && [ "$(git rev-parse "$latest^{}")" = "$head" ]; then
10+ printf '%s\n' "$latest"
11+ exit 0
12+fi
13+
14+if [ -n "$latest" ]; then
15+ range="${latest}..HEAD"
16+else
17+ range="HEAD"
18+fi
19+
20+minor=false
21+patch=false
22+while IFS= read -r commit; do
23+ subject=$(git log -1 --format=%s "$commit")
24+ body=$(git log -1 --format=%b "$commit")
25+
26+ # A breaking change is a v0.x minor release.
27+ if grep -Eq '^[[:alpha:]]+(\([^)]+\))?!:' <<<"$subject" || grep -Eiq '^BREAKING[ -]CHANGE:' <<<"$body"; then
28+ minor=true
29+ continue
30+ fi
31+
32+ type=${subject%%(*}
33+ type=${type%%:*}
34+ type=${type%%!*}
35+ type=$(tr '[:upper:]' '[:lower:]' <<<"$type")
36+ case "$type" in
37+ feat) minor=true ;;
38+ fix|perf|refactor|build|chore|ci) patch=true ;;
39+ docs|test|style) ;;
40+ *) ;;
41+ esac
42+done < <(git rev-list "$range")
43+
44+if [ "$minor" != true ] && [ "$patch" != true ]; then
45+ exit 0
46+fi
47+
48+# The renamed project starts a new public release line at v0.1.0.
49+if [ -z "$latest" ]; then
50+ printf '%s\n' 'v0.1.0'
51+ exit 0
52+fi
53+
54+version=${latest#v}
55+IFS=. read -r major minor_version patch_version <<<"$version"
56+if [ "$minor" = true ]; then
57+ printf 'v%s.%s.0\n' "$major" "$((minor_version + 1))"
58+else
59+ printf 'v%s.%s.%s\n' "$major" "$minor_version" "$((patch_version + 1))"
60+fi
A .github/scripts/next-release-tag_test.sh +39 -0
@@ -0,0 +1,39 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+
4+repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
5+script="$repo_root/.github/scripts/next-release-tag.sh"
6+tmp=$(mktemp -d)
7+trap 'rm -rf "$tmp"' EXIT
8+
9+cd "$tmp"
10+git init --quiet
11+git config user.name 'Release Test'
12+git config user.email 'release-test@example.invalid'
13+
14+git commit --allow-empty --quiet -m 'feat: first release'
15+expect() {
16+ local want=$1
17+ local got
18+ got=$($script)
19+ if [ "$got" != "$want" ]; then
20+ printf 'next-release-tag = %q, want %q\n' "$got" "$want" >&2
21+ exit 1
22+ fi
23+}
24+
25+expect 'v0.1.0'
26+git tag -a v0.1.0 -m 'release v0.1.0'
27+
28+# A rerun after tag creation must release the existing tag, not skip it.
29+expect 'v0.1.0'
30+
31+git commit --allow-empty --quiet -m 'docs: explain releases'
32+expect ''
33+
34+git commit --allow-empty --quiet -m 'fix: repair installer'
35+expect 'v0.1.1'
36+git tag -a v0.1.1 -m 'release v0.1.1'
37+
38+git commit --allow-empty --quiet -m 'refactor!: replace extension handshake'
39+expect 'v0.2.0'
M .github/workflows/ci.yml +3 -0
@@ -53,6 +53,9 @@ jobs:
5353 steps:
5454 - uses: actions/checkout@v4
5555
56+ - name: release-version policy
57+ run: bash .github/scripts/next-release-tag_test.sh
58+
5659 - name: set up go
5760 uses: actions/setup-go@v7
5861 with:
M .github/workflows/release.yml +36 -71
@@ -1,18 +1,8 @@
11 name: release
22
3-# Every push to main that passes CI cuts a new release. The workflow:
4-# 1. waits for the ci workflow on the same commit to succeed
5-# 2. looks up the latest existing vX.Y.Z tag
6-# 3. bumps the patch number (vX.Y.Z+1) — or starts at v0.0.1 if none
7-# 4. creates and pushes that tag
8-# 5. runs goreleaser, which builds binaries for linux/darwin/windows
9-# (amd64 + arm64) and attaches them plus checksums.txt to a fresh
10-# GitHub Release page
11-#
12-# Skip a release by putting [release=skip] anywhere in the commit
13-# message — useful for docs-only or ci-only commits. We use a rare
14-# equals-sign form so commits that merely *mention* the feature
15-# ("make the release step optional") don't accidentally opt out.
3+# After CI succeeds on main, create at most one tag for the unreleased
4+# conventional commits. The tag is then built by the shared GoReleaser
5+# workflow at its exact commit.
166 on:
177 workflow_run:
188 workflows: [ci]
@@ -20,85 +10,60 @@ on:
2010 branches: [main]
2111
2212 permissions:
23- # Needed to create tags and to upload release archives.
2413 contents: write
2514
26-# Only one release job at a time. If two pushes land in quick
27-# succession, queue them so they don't race on the tag.
2815 concurrency:
2916 group: release
3017 cancel-in-progress: false
3118
3219 jobs:
33- release:
34- # Only run when ci succeeded and we're on main, and when the head
35- # commit doesn't opt out via [skip-release].
36- if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' && !contains(github.event.workflow_run.head_commit.message, '[release=skip]') }}
20+ tag:
21+ if: >-
22+ github.event.workflow_run.conclusion == 'success' &&
23+ github.event.workflow_run.head_branch == 'main' &&
24+ !contains(github.event.workflow_run.head_commit.message, '[release=skip]')
3725 runs-on: ubuntu-latest
26+ outputs:
27+ tag: ${{ steps.version.outputs.tag }}
3828 steps:
39- - name: checkout
29+ - name: Check out the verified commit
4030 uses: actions/checkout@v4
4131 with:
42- # Full history so we can read existing tags.
4332 fetch-depth: 0
44- # Check out the exact sha that ci verified, not whatever the
45- # branch tip happens to be at this moment (which may have
46- # advanced since ci finished).
33+ fetch-tags: true
4734 ref: ${{ github.event.workflow_run.head_sha }}
4835
49- - name: compute next version
50- id: bump
36+ - name: Compute the next release tag
37+ id: version
5138 shell: bash
5239 run: |
5340 set -euo pipefail
54- # Find the newest vX.Y.Z tag. If the repo has none yet, start
55- # at v0.0.0 so the first release becomes v0.0.1.
56- latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1)
57- if [ -z "$latest" ]; then
58- latest="v0.0.0"
59- fi
60- echo "latest tag: $latest"
61-
62- # Strip the v prefix, split on dots, bump the patch.
63- # After .99 roll over to the next minor (e.g. 0.0.99 -> 0.1.0,
64- # 0.1.99 -> 0.2.0). Keep major fixed at 0 for now.
65- ver="${latest#v}"
66- IFS=. read -r major minor patch <<<"$ver"
67- if [ "$patch" -ge 99 ]; then
68- minor=$((minor + 1))
69- patch=0
70- else
71- patch=$((patch + 1))
41+ tag=$(bash .github/scripts/next-release-tag.sh)
42+ if [ -z "$tag" ]; then
43+ echo "No releasable conventional commits for this run."
44+ exit 0
7245 fi
73- next="v${major}.${minor}.${patch}"
74- echo "next tag: $next"
46+ echo "Releasing $tag from $(git rev-parse --short HEAD)"
47+ echo "tag=$tag" >> "$GITHUB_OUTPUT"
7548
76- echo "next=$next" >> "$GITHUB_OUTPUT"
77- echo "version=${next#v}" >> "$GITHUB_OUTPUT"
78-
79- - name: create and push tag
49+ - name: Create and push the tag
50+ if: steps.version.outputs.tag != ''
8051 env:
81- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52+ TAG: ${{ steps.version.outputs.tag }}
8253 run: |
8354 git config user.name "github-actions[bot]"
8455 git config user.email "github-actions[bot]@users.noreply.github.com"
85- git tag -a "${{ steps.bump.outputs.next }}" \
86- -m "release ${{ steps.bump.outputs.next }}"
87- git push origin "${{ steps.bump.outputs.next }}"
56+ git tag -a "$TAG" -m "release $TAG"
57+ git push origin "$TAG"
8858
89- - name: set up go
90- uses: actions/setup-go@v7
91- with:
92- go-version: "1.26"
93- cache: true
94-
95- - name: run goreleaser
96- uses: goreleaser/goreleaser-action@v6
97- with:
98- distribution: goreleaser
99- version: latest
100- args: release --clean
101- env:
102- # goreleaser reads the tag we just pushed and builds binaries
103- # for every (goos, goarch) combination in .goreleaser.yaml.
104- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59+ release:
60+ needs: tag
61+ if: needs.tag.outputs.tag != ''
62+ permissions:
63+ contents: write
64+ uses: bnema/gh-actions/.github/workflows/go-release.yml@main
65+ with:
66+ go-version: "1.26"
67+ # The reusable workflow fetches tags before this command, ensuring
68+ # GoReleaser produces artifacts and notes for the newly created tag.
69+ pre-command: git checkout --detach "${{ needs.tag.outputs.tag }}"
M .goreleaser.yaml +38 -33
@@ -1,23 +1,18 @@
11 # GoReleaser config — builds cross-platform binaries and GitHub Release
2-# artifacts for zot.
3-#
4-# Triggered by pushing a tag like `v0.1.0`. The release workflow runs
5-# `goreleaser release --clean`, which compiles every target, packs
6-# them into `.tar.gz` (Unix) / `.zip` (Windows) archives, generates a
7-# checksum file, and uploads everything to a GitHub Release.
8-
2+# artifacts for zut. The release workflow creates conventional-commit-based
3+# v0.x tags, then invokes GoReleaser for that immutable tag.
94 version: 2
105
11-project_name: zot
6+project_name: zut
127
138 before:
149 hooks:
1510 - go mod tidy
1611
1712 builds:
18- - id: zot
19- main: ./cmd/zot
20- binary: zot
13+ - id: zut
14+ main: ./cmd/zut
15+ binary: zut
2116 env:
2217 - CGO_ENABLED=0
2318 goos: [linux, darwin, windows]
@@ -36,59 +31,69 @@ builds:
3631 - -X main.date={{.Date}}
3732
3833 archives:
39- - id: zot
40- # Human-friendly name: zot_0.1.0_darwin_arm64.tar.gz
34+ - id: zut
35+ # Human-friendly name: zut_0.1.0_darwin_arm64.tar.gz
4136 name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
4237 files:
4338 - LICENSE
4439 - README.md
4540 format_overrides:
4641 - goos: windows
47- format: zip
42+ formats: [zip]
4843
4944 checksum:
5045 name_template: "checksums.txt"
5146 algorithm: sha256
5247
5348 snapshot:
54- # Used by `goreleaser release --snapshot`; lets contributors build
55- # a local release without pushing a tag.
5649 version_template: "{{ incpatch .Version }}-next"
5750
51+git:
52+ # Generate prerelease changelogs from the previous stable tag.
53+ tag_sort: smartsemver
54+
5855 changelog:
5956 sort: asc
6057 use: github
6158 filters:
59+ # Styling-only commits are neither releasable nor useful release notes.
6260 exclude:
63- - "^docs:"
64- - "^chore:"
65- - "^ci:"
66- - "^test:"
61+ - "^style:"
6762 groups:
68- - title: features
69- regexp: "^.*feat[(\\w)]*:+.*$"
63+ - title: Added
64+ regexp: "^feat(\\([^)]+\\))?!?:"
7065 order: 0
71- - title: fixes
72- regexp: "^.*fix[(\\w)]*:+.*$"
66+ - title: Fixed
67+ regexp: "^fix(\\([^)]+\\))?!?:"
7368 order: 1
74- - title: other
69+ - title: Changed
70+ regexp: "^(perf|refactor|build)(\\([^)]+\\))?!?:"
71+ order: 2
72+ - title: Maintenance
73+ regexp: "^(chore|ci)(\\([^)]+\\))?!?:"
74+ order: 3
75+ - title: Documentation
76+ regexp: "^docs(\\([^)]+\\))?!?:"
77+ order: 4
78+ - title: Tests
79+ regexp: "^test(\\([^)]+\\))?!?:"
80+ order: 5
81+ - title: Other changes
7582 order: 999
7683
7784 release:
7885 github:
79- owner: patriceckhart
80- name: zot
86+ owner: bnema
87+ name: zut
8188 draft: false
8289 prerelease: auto
83- # Auto-generated release notes include the changelog groups above plus
84- # an install snippet so the release page itself is a landing page.
8590 header: |
86- ## zot {{ .Tag }}
91+ ## zut {{ .Tag }}
8792
88- one-liner install:
93+ Install with:
8994
9095 ```bash
91- curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash
96+ curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash
9297 ```
9398
94- or download a binary below, `chmod +x`, and drop it on your `$PATH`.
99+ Or download a binary below, verify it with `checksums.txt`, and add it to your `$PATH`.
M AGENTS.md +6 -6
@@ -1,10 +1,10 @@
1-# Working Agreement for zot
1+# Working Agreement for zut
22
33 This file defines how automated coding assistants should work in this repository. Treat it as a practical operating manual, not a substitute for reading the code.
44
55 ## Product intent
66
7-zot is a compact Go coding-agent harness. Changes should preserve its defining properties:
7+zut is a compact Go coding-agent harness. Changes should preserve its defining properties:
88
99 - one portable binary
1010 - a provider-neutral agent engine
@@ -36,7 +36,7 @@ Put behavior in the package that owns the concern:
3636 | `packages/core` | Agent loop, messages, tool contracts, events, confirmation, sessions, compaction |
3737 | `packages/provider` | Provider clients, wire formats, streaming, retries, model metadata and discovery |
3838 | `packages/provider/auth` | Login flows, credential lookup, refresh, and storage |
39-| `packages/agent` | CLI configuration, model selection, runtime assembly, RPC, and zotfiles |
39+| `packages/agent` | CLI configuration, model selection, runtime assembly, RPC, and zutfiles |
4040 | `packages/agent/modes` | Interactive, print, JSON, dialog, bot, and Telegram interfaces |
4141 | `packages/agent/extensions` | Extension process lifecycle and host integration |
4242 | `packages/agent/extproto` | Extension wire protocol |
@@ -51,7 +51,7 @@ Provider quirks must not leak into `packages/core`. Terminal escape handling and
5151
5252 ## Correctness contracts
5353
54-Some parts of zot require extra care because small mistakes corrupt state or weaken user protections.
54+Some parts of zut require extra care because small mistakes corrupt state or weaken user protections.
5555
5656 ### Agent events and sessions
5757
@@ -148,13 +148,13 @@ Documentation-only changes do not require the Go test suite unless they alter ge
148148
149149 ## Documentation duties
150150
151-Update documentation in the same change whenever users, integrators, or extension authors would observe different behavior. Relevant locations include `README.md`, `docs/rpc.md`, `docs/extensions.md`, `docs/skills.md`, `docs/themes.md`, and `docs/zotfiles.md`.
151+Update documentation in the same change whenever users, integrators, or extension authors would observe different behavior. Relevant locations include `README.md`, `docs/rpc.md`, `docs/extensions.md`, `docs/skills.md`, `docs/themes.md`, and `docs/zutfiles.md`.
152152
153153 Examples must match real flags and schemas. State limitations plainly, especially around sandboxing, provider support, compatibility, and platform differences.
154154
155155 ## Dependency policy
156156
157-zot's single-binary design and small footprint are product requirements.
157+zut's single-binary design and small footprint are product requirements.
158158
159159 - Prefer the standard library or existing dependencies.
160160 - Explain why a new dependency is necessary before adding it.
M CONTRIBUTING.md +9 -9
@@ -1,4 +1,4 @@
1-# Contributing to zot
1+# Contributing to zut
22
33 Contributions are welcome, including bug reports, design feedback, documentation, tests, and code. This guide explains what makes a contribution easy to evaluate and safe to merge.
44
@@ -8,7 +8,7 @@ Contributions are welcome, including bug reports, design feedback, documentation
88
99 Use the bug-report template and provide enough information for someone else to reproduce the problem:
1010
11-- the output of `zot --version`
11+- the output of `zut --version`
1212 - operating system and terminal
1313 - provider and model, when relevant
1414 - the smallest sequence of actions that triggers the problem
@@ -35,14 +35,14 @@ Focused typo, documentation, and test improvements can usually go directly to a
3535
3636 ## Design boundaries
3737
38-zot is intended to remain a lightweight, single-binary application. A contribution should fit that direction rather than increasing scope by default.
38+zut is intended to remain a lightweight, single-binary application. A contribution should fit that direction rather than increasing scope by default.
3939
4040 The major ownership boundaries are:
4141
4242 - `packages/core`: provider-independent agent behavior and session structures
4343 - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior
4444 - `packages/provider/auth`: credential and login handling
45-- `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and zotfiles
45+- `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and zutfiles
4646 - `packages/agent/modes`: interactive and non-interactive user modes
4747 - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK
4848 - `packages/agent/skills`: reusable instruction discovery
@@ -62,14 +62,14 @@ Use the Go version declared in `go.mod`, then build from the repository root:
6262 ```sh
6363 go mod download
6464 make build
65-./bin/zot --version
65+./bin/zut --version
6666 ```
6767
6868 Useful commands:
6969
7070 ```sh
7171 make help # list the important developer commands
72-make build # compile ./bin/zot as 0.0.0-dev
72+make build # compile ./bin/zut as 0.0.0-dev
7373 make run # build and start the interactive application
7474 make install # install the current checkout with go install
7575 make go-install # install a published module version
@@ -79,7 +79,7 @@ make lint # run golangci-lint, go vet, and verify gofmt
7979 make lint-install # install the pinned golangci-lint version
8080 ```
8181
82-Tests must not depend on real provider credentials, paid model calls, a developer's global zot state, or a specific terminal. Use local HTTP test servers, fixtures, temporary directories, and synthetic credentials.
82+Tests must not depend on real provider credentials, paid model calls, a developer's global zut state, or a specific terminal. Use local HTTP test servers, fixtures, temporary directories, and synthetic credentials.
8383
8484 ## Making a change
8585
@@ -150,7 +150,7 @@ If a validation command cannot pass because of an unrelated repository or enviro
150150 Open the pull request against `main` and include:
151151
152152 - the problem being addressed
153-- the chosen solution and why it belongs in zot
153+- the chosen solution and why it belongs in zut
154154 - tests added or updated
155155 - commands used for verification
156156 - user-visible, compatibility, and platform effects
@@ -170,7 +170,7 @@ Choose the document that owns the public surface:
170170 - `docs/extensions.md` for extension authors
171171 - `docs/skills.md` for reusable skills
172172 - `docs/themes.md` for theme files
173-- `docs/zotfiles.md` for portable agents
173+- `docs/zutfiles.md` for portable agents
174174
175175 Examples should be runnable and limitations should be stated directly.
176176
M Makefile +5 -5
@@ -3,8 +3,8 @@
33 .DEFAULT_GOAL := help
44
55 GO ?= go
6-PKG ?= ./cmd/zot
7-BINARY ?= zot
6+PKG ?= ./cmd/zut
7+BINARY ?= zut
88 BIN_DIR ?= bin
99 DEV_VERSION ?= 0.0.0-dev
1010 VERSION ?= $(DEV_VERSION)
@@ -17,7 +17,7 @@ LDFLAGS := -s -w -X main.version=$(VERSION)
1717
1818 help:
1919 @printf '%s\n' \
20- 'build build ./bin/zot' \
20+ 'build build ./bin/zut' \
2121 'install install the current checkout with go install' \
2222 'go-install install a published module version (GO_INSTALL_VERSION=latest)' \
2323 'test run all tests with the race detector' \
@@ -27,7 +27,7 @@ help:
2727 'lint-install install golangci-lint $(GOLANGCI_LINT_VERSION)' \
2828 'fmt format Go source files' \
2929 'check run test-fast and lint' \
30- 'run build and run ./bin/zot (pass ARGS="...")' \
30+ 'run build and run ./bin/zut (pass ARGS="...")' \
3131 'clean remove build output' \
3232 'release cross-build release binaries (set VERSION=...)'
3333
@@ -41,7 +41,7 @@ install:
4141
4242 # Install a published/module version, rather than the current checkout.
4343 go-install:
44- $(GO) install github.com/patriceckhart/zot/cmd/zot@$(GO_INSTALL_VERSION)
44+ $(GO) install github.com/bnema/zut/cmd/zut@$(GO_INSTALL_VERSION)
4545
4646 test:
4747 $(GO) test -race ./...
M README.md +191 -188
@@ -1,6 +1,6 @@
11 <div align="center">
2- <a href="https://www.zot.sh">
3- <img src="packages/provider/auth/assets/zot-logo.png" alt="zot coding agent harness" width="130" height="130" />
2+ <a href="https://github.com/bnema/zut">
3+ <img src="packages/provider/auth/assets/zut-logo.png" alt="zut coding agent harness" width="130" height="130" />
44 </a>
55 </div>
66 <br>
@@ -10,9 +10,12 @@
1010 <img alt="30+ providers" src="https://img.shields.io/badge/providers-30+-34E2EA">
1111 </p>
1212 <p align="center">
13- <a href="https://www.zot.sh">zot.sh</a>
13+ <a href="https://github.com/bnema/zut">github.com/bnema/zut</a>
1414 </p>
1515
16+> [!INFO]
17+> **Fork notice:** `zut` is an independently maintained fork of [zot](https://github.com/patriceckhart/zot). It retains the upstream MIT license and copyright notice; see [LICENSE](LICENSE).
18+
1619 ## What is it?
1720
1821 Yet another coding agent harness, lightweight and written (vibe-slopped) in go.
@@ -22,50 +25,50 @@ Yet another coding agent harness, lightweight and written (vibe-slopped) in go.
2225 - five core built-in tools (read, write, edit, bash, and lsp); the conditional `skill` tool is available when skills are enabled.
2326 - three run modes (interactive tui, print, json).
2427 - built-in telegram bot.
25-- extensions in any language via subprocess + json-rpc. None installed by default; opt in with `zot ext install` or `zot --ext`. See [docs/extensions.md](docs/extensions.md).
28+- extensions in any language via subprocess + json-rpc. None installed by default; opt in with `zut ext install` or `zut --ext`. See [docs/extensions.md](docs/extensions.md).
2629 - user and extension themes via JSON; see [docs/themes.md](docs/themes.md).
2730 - standing instructions via `AGENTS.md` files (global and per-project); see [Persistent instructions](#persistent-instructions-agentsmd).
2831 - reusable instructions via `SKILL.md` files; see [docs/skills.md](docs/skills.md).
2932 - named subagent profiles from `~/.agents/agents/*.md`; see [docs/subagents.md](docs/subagents.md).
30-- portable agents from local directories, `.zot` files, or temporary public GitHub downloads; see [docs/zotfiles.md](docs/zotfiles.md).
33+- portable agents from local directories, `.zut` files, or temporary public GitHub downloads; see [docs/zutfiles.md](docs/zutfiles.md).
3134
3235 ## Install
3336
3437 ### One-liner (macOS, Linux)
3538
3639 ```bash
37-curl -fsSL https://www.zot.sh/install.sh | bash
40+curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash
3841 ```
3942
4043 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:
4144
4245 ```bash
43-curl -fsSL https://www.zot.sh/install.sh | bash -s -- v0.0.1 ~/bin
46+curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash -s -- v0.1.0 ~/bin
4447 ```
4548
4649 ### One-liner (Windows, PowerShell)
4750
4851 ```powershell
49-iwr -useb https://www.zot.sh/install.ps1 | iex
52+iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex
5053 ```
5154
52-Drops `zot.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards.
55+Drops `zut.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards.
5356
5457 ### go install
5558
5659 ```bash
57-go install github.com/patriceckhart/zot/cmd/zot@latest
60+go install github.com/bnema/zut/cmd/zut@latest
5861 ```
5962
60-The installed binary reports the tagged module version and supports `zot update`.
63+The installed binary reports the tagged module version and supports `zut update`.
6164
6265 ### From source
6366
6467 ```bash
65-git clone https://github.com/patriceckhart/zot
66-cd zot
68+git clone https://github.com/bnema/zut
69+cd zut
6770 make help # list the important developer commands
68-make build # produces ./bin/zot (0.0.0-dev by default)
71+make build # produces ./bin/zut (0.0.0-dev by default)
6972 make install # install the current checkout with go install
7073 make go-install # install the latest published module version
7174 ```
@@ -76,23 +79,23 @@ development build.
7679
7780 ### Prebuilt binaries
7881
79-Every release on the [releases page](https://github.com/patriceckhart/zot/releases) ships archives for Linux, macOS, and Windows on amd64 and arm64 (except windows/arm64), plus a `checksums.txt` file. Download, verify, `chmod +x`, and drop on your `$PATH`.
82+Every release on the [releases page](https://github.com/bnema/zut/releases) ships archives for Linux, macOS, and Windows on amd64 and arm64 (except windows/arm64), plus a `checksums.txt` file. Download, verify, `chmod +x`, and drop on your `$PATH`.
8083
8184 ## Authenticate
8285
83-The easiest way is to just run `zot` and type `/login`. The TUI opens even without credentials and walks you through a browser-based login flow.
86+The easiest way is to just run `zut` and type `/login`. The TUI opens even without credentials and walks you through a browser-based login flow.
8487
8588 ### Credential lookup order
8689
8790 1. `--api-key` flag
8891 2. provider-specific env var (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `KIMI_API_KEY`, `MOONSHOT_API_KEY`, `DEEPSEEK_API_KEY`, `GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GROQ_API_KEY`, `OPENROUTER_API_KEY`, `MISTRAL_API_KEY`, `XAI_API_KEY`, `CEREBRAS_API_KEY`, `TOGETHER_API_KEY`, `HF_TOKEN`, `ZAI_API_KEY`, `XIAOMI_API_KEY`, `MINIMAX_API_KEY`, `FIREWORKS_API_KEY`, `AI_GATEWAY_API_KEY`, `COPILOT_GITHUB_TOKEN`, `GITHUB_COPILOT_TOKEN`, and others for provider-specific backends)
89-3. `$ZOT_HOME/auth.json` (API key or OAuth token; mode 0600)
92+3. `$ZUT_HOME/auth.json` (API key or OAuth token; mode 0600)
9093
91-`$ZOT_HOME` defaults to:
92-- All platforms: `$XDG_STATE_HOME/zot` when `XDG_STATE_HOME` is set
93-- macOS fallback: `~/Library/Application Support/zot`
94-- Linux fallback: `~/.local/state/zot`
95-- Windows fallback: `%LOCALAPPDATA%\zot`
94+`$ZUT_HOME` defaults to:
95+- All platforms: `$XDG_STATE_HOME/zut` when `XDG_STATE_HOME` is set
96+- macOS fallback: `~/Library/Application Support/zut`
97+- Linux fallback: `~/.local/state/zut`
98+- Windows fallback: `%LOCALAPPDATA%\zut`
9699
97100 ### API keys from commands
98101
@@ -112,37 +115,37 @@ To keep an API key in a password manager instead of `auth.json`, configure an `a
112115
113116 For a provider added through `models.json`, put the same credential object under `additional_api_key_creds` using its provider ID. `program` is executed directly, without a shell, so each argument must be a separate `args` entry. `timeout_ms` is optional and defaults to 120 seconds.
114117
115-zot runs the command only when that provider is selected, not while checking login status or refreshing model catalogs in the background. Successful output is cached in memory for the rest of the zot process and is never written to disk. The command must print one non-empty line to stdout; zot removes trailing CR/LF characters, limits output to 64 KiB, and does not include command output in errors. Saving a normal key through `/login` replaces the command configuration, and `/logout` removes it.
118+zut runs the command only when that provider is selected, not while checking login status or refreshing model catalogs in the background. Successful output is cached in memory for the rest of the zut process and is never written to disk. The command must print one non-empty line to stdout; zut removes trailing CR/LF characters, limits output to 64 KiB, and does not include command output in errors. Saving a normal key through `/login` replaces the command configuration, and `/logout` removes it.
116119
117-Treat `auth.json` as executable configuration: anyone who can modify it can cause zot to run a program under your user account. zot does not interpret `!` prefixes or execute command strings through a shell.
120+Treat `auth.json` as executable configuration: anyone who can modify it can cause zut to run a program under your user account. zut does not interpret `!` prefixes or execute command strings through a shell.
118121
119122 ### `/login` flow
120123
121-Run `zot` and type `/login`. Pick one of two methods:
124+Run `zut` and type `/login`. Pick one of two methods:
122125
123-- **API key**: a small local web server starts on `127.0.0.1:<free-port>`, your browser opens a form, you pick a provider from the full API-key provider list, paste the key, and zot saves it to `auth.json` if accepted. Providers with a lightweight model-list endpoint are probed before saving; provider backends that need extra project/account env vars are saved directly.
126+- **API key**: a small local web server starts on `127.0.0.1:<free-port>`, your browser opens a form, you pick a provider from the full API-key provider list, paste the key, and zut saves it to `auth.json` if accepted. Providers with a lightweight model-list endpoint are probed before saving; provider backends that need extra project/account env vars are saved directly.
124127 - **Subscription**: use your Claude Pro/Max, ChatGPT Plus/Pro, Kimi Code, SuperGrok/X Premium, or GitHub Copilot subscription. DeepSeek and Google Gemini do **not** have a subscription login path. For those, use the API-key flow.
125128 - Anthropic and OpenAI pin the browser callback to fixed provider-specific ports (`localhost:53692` for Anthropic, `localhost:1455` for OpenAI) because those are the only ports their auth servers will redirect to.
126129 - Anthropic uses the Claude Code OAuth flow. Messages go to `api.anthropic.com` with a bearer token and the Claude Code identity headers.
127130 - OpenAI uses the Codex CLI OAuth flow. Messages go to `chatgpt.com/backend-api/codex/responses` with the `chatgpt-account-id` extracted from the returned id_token.
128- - Kimi uses the Kimi Code device-code OAuth flow. zot opens the verification URL, polls until you approve it in the browser, then sends messages to `api.kimi.com/coding/v1` with the Kimi Code identity headers.
129- - xAI uses a device-code OAuth flow. zot opens a prefilled authorization URL, polls for approval, and uses the resulting token with the xAI API.
130- - GitHub Copilot uses GitHub's device-code login flow. zot stores the GitHub access token and exchanges it for short-lived Copilot inference tokens on demand.
131+ - Kimi uses the Kimi Code device-code OAuth flow. zut opens the verification URL, polls until you approve it in the browser, then sends messages to `api.kimi.com/coding/v1` with the Kimi Code identity headers.
132+ - xAI uses a device-code OAuth flow. zut opens a prefilled authorization URL, polls for approval, and uses the resulting token with the xAI API.
133+ - GitHub Copilot uses GitHub's device-code login flow. zut stores the GitHub access token and exchanges it for short-lived Copilot inference tokens on demand.
131134
132135 > **Note on subscription login.** The OAuth client IDs used are the ones published in Anthropic's Claude Code CLI, OpenAI's Codex CLI, Kimi Code CLI, xAI's device flow, and GitHub Copilot's device-code flow. Reusing them from a third-party tool may be against their terms of service and may be revoked at any time. Use it at your own risk; the API-key flow is the safe default.
133136
134137 ### Token refresh
135138
136-OAuth access tokens are short-lived (Anthropic ~8h, OpenAI ~30d; Kimi, xAI, and GitHub Copilot also use refresh/exchange flows). zot refreshes or exchanges them automatically:
139+OAuth access tokens are short-lived (Anthropic ~8h, OpenAI ~30d; Kimi, xAI, and GitHub Copilot also use refresh/exchange flows). zut refreshes or exchanges them automatically:
137140
138-- At every credential lookup, zot checks the stored `expiry` and, if past it (with a 60s safety margin), hits the provider's `oauth/token` endpoint with the stored `refresh_token`, persists the new `access_token`, `refresh_token`, and `expiry` back to `auth.json`, and hands the fresh token to the client.
141+- At every credential lookup, zut checks the stored `expiry` and, if past it (with a 60s safety margin), hits the provider's `oauth/token` endpoint with the stored `refresh_token`, persists the new `access_token`, `refresh_token`, and `expiry` back to `auth.json`, and hands the fresh token to the client.
139142 - The telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.
140143 - If the refresh itself fails (the `refresh_token` was revoked, or the account was logged out everywhere), the error bubbles up to the caller: the TUI shows it in the status line, the bot replies with it in your DM. Run `/login` to get a fresh token pair.
141144
142-All data lives under `$ZOT_HOME`:
145+All data lives under `$ZUT_HOME`:
143146
144147 ```
145-$ZOT_HOME/
148+$ZUT_HOME/
146149 ├── config.json # last-used provider/model/theme and persistent settings
147150 ├── auth.json # api keys and oauth tokens (mode 0600)
148151 ├── sessions/ # jsonl transcripts, one dir per cwd
@@ -155,24 +158,24 @@ $ZOT_HOME/
155158 └── logs/ # app log files
156159 ```
157160
158-Drop a `SYSTEM.md` in `$ZOT_HOME` to replace the built-in identity for every run. Existing append addenda, including `AGENTS.md`, skills, and enabled Ponytail coding mode, remain layered on top. `--system-prompt` wins per invocation unless a selected replace-mode subagent profile supplies the child's identity. Delete the file to revert to the default identity.
161+Drop a `SYSTEM.md` in `$ZUT_HOME` to replace the built-in identity for every run. Existing append addenda, including `AGENTS.md`, skills, and enabled Ponytail coding mode, remain layered on top. `--system-prompt` wins per invocation unless a selected replace-mode subagent profile supplies the child's identity. Delete the file to revert to the default identity.
159162
160-Ponytail coding mode is enabled by default, including when an existing `config.json` does not contain a `ponytail_enabled` field. When disabled, its compact guidance is omitted from resolved system prompts; when enabled, the guidance tells the model to apply itself only to engineering work. The setting persists its explicit on/off choice in `$ZOT_HOME/config.json` and can be changed from `/settings`. It is advisory guidance only: it does not change tool permissions, confirmations, jail behavior, or sandboxing.
163+Ponytail coding mode is enabled by default, including when an existing `config.json` does not contain a `ponytail_enabled` field. When disabled, its compact guidance is omitted from resolved system prompts; when enabled, the guidance tells the model to apply itself only to engineering work. The setting persists its explicit on/off choice in `$ZUT_HOME/config.json` and can be changed from `/settings`. It is advisory guidance only: it does not change tool permissions, confirmations, jail behavior, or sandboxing.
161164
162165 A selected subagent profile with `systemPromptMode: replace` supplies the child's base identity even when `--system-prompt` was provided for the parent run. Global append addenda, including enabled Ponytail guidance, still follow their normal inclusion rules.
163166
164167 ## Persistent instructions (AGENTS.md)
165168
166-Use `AGENTS.md` to give zot standing instructions that layer **on top of** the default system prompt, without replacing it. This is the friendliest way to shape behavior (for example, taming local models that jump straight to code edits) because it adds guidance rather than taking over the base identity the way `SYSTEM.md` does.
169+Use `AGENTS.md` to give zut standing instructions that layer **on top of** the default system prompt, without replacing it. This is the friendliest way to shape behavior (for example, taming local models that jump straight to code edits) because it adds guidance rather than taking over the base identity the way `SYSTEM.md` does.
167170
168-zot discovers `AGENTS.md` files automatically at startup and loads them in this order:
171+zut discovers `AGENTS.md` files automatically at startup and loads them in this order:
169172
170-1. `$ZOT_HOME/AGENTS.md` (global, machine-wide instructions that apply to every project).
173+1. `$ZUT_HOME/AGENTS.md` (global, machine-wide instructions that apply to every project).
171174 2. Every `AGENTS.md` from the filesystem root down to the current working directory. More specific (deeper) files may override earlier ones. This includes `~/AGENTS.md` when the working directory is inside your home directory.
172175
173-All discovered files are appended to the prompt in that order, so a global baseline can be refined per project. To list the active Zotfile agent, loaded context paths, extensions, and user-installed skills above the interactive transcript, enable **show loaded resources at startup** in `/settings`. The agent section appears only for `zot run`, the setting is off by default, built-in skills are omitted, and the displayed lists are not sent to the model or stored in the session transcript.
176+All discovered files are appended to the prompt in that order, so a global baseline can be refined per project. To list the active Zutfile agent, loaded context paths, extensions, and user-installed skills above the interactive transcript, enable **show loaded resources at startup** in `/settings`. The agent section appears only for `zut run`, the setting is off by default, built-in skills are omitted, and the displayed lists are not sent to the model or stored in the session transcript.
174177
175-For a general, non-project-specific instruction set, put your rules in `$ZOT_HOME/AGENTS.md`, for example:
178+For a general, non-project-specific instruction set, put your rules in `$ZUT_HOME/AGENTS.md`, for example:
176179
177180 ```markdown
178181 Treat questions and discussions as requests for explanation. Do not edit files or run tools unless explicitly asked to make a change. Ask before modifying code.
@@ -182,30 +185,30 @@ Treat questions and discussions as requests for explanation. Do not edit files o
182185
183186 | Mechanism | Scope | Effect |
184187 |---|---|---|
185-| `$ZOT_HOME/AGENTS.md` | global, every run | appended to the default prompt |
188+| `$ZUT_HOME/AGENTS.md` | global, every run | appended to the default prompt |
186189 | `./AGENTS.md` (and parent dirs) | project | appended to the default prompt |
187-| `$ZOT_HOME/SYSTEM.md` | global, every run | replaces the built-in identity; append addenda remain |
190+| `$ZUT_HOME/SYSTEM.md` | global, every run | replaces the built-in identity; append addenda remain |
188191 | `--append-system-prompt <text>` | single run | appends for one invocation (repeatable) |
189192 | `--system-prompt <text>` | single run | replaces the built-in identity for one invocation; append addenda remain |
190193
191-> **Note:** zot does not read a `CLAUDE.md` instruction file. The only Claude-compatible thing it picks up is skills under `.claude/skills/`. If you are migrating from Claude Code, move that content into `AGENTS.md` (global or per-project) and zot will use it.
194+> **Note:** zut does not read a `CLAUDE.md` instruction file. The only Claude-compatible thing it picks up is skills under `.claude/skills/`. If you are migrating from Claude Code, move that content into `AGENTS.md` (global or per-project) and zut will use it.
192195
193196 ## Changelog on update
194197
195-The first time you launch a newer zot binary, the TUI shows the GitHub release notes once in a dismissible overlay. Press any key to close. The version is recorded in `config.json`'s `last_changelog_shown` so the same release notes never reappear. Fresh installs don't see a changelog (no upgrade has happened yet). Development builds (`0.0.0-dev`) skip both the update and changelog requests and print `dev version detected; skipping update check`. For release builds, the fetch is best-effort: a network failure or a missing release page silently skips, with another attempt on the next launch.
198+The first time you launch a newer zut binary, the TUI shows the GitHub release notes once in a dismissible overlay. Press any key to close. The version is recorded in `config.json`'s `last_changelog_shown` so the same release notes never reappear. Fresh installs don't see a changelog (no upgrade has happened yet). Development builds (`0.0.0-dev`) skip both the update and changelog requests and print `dev version detected; skipping update check`. For release builds, the fetch is best-effort: a network failure or a missing release page silently skips, with another attempt on the next launch.
196199
197200 ## Usage
198201
199202 ```bash
200-zot # interactive tui
201-zot "fix the failing test" # tui, pre-filled prompt
202-zot -p "list all go files" # print final text, exit
203-zot -p --stats stats.json "task" # print final text and write generation stats
204-zot --json "refactor main.go" # newline-delimited json events, exit
205-zot --continue # resume the most recent session for this cwd
206-zot --resume # pick a session to resume
207-zot --list-models # show supported models
208-zot --help
203+zut # interactive tui
204+zut "fix the failing test" # tui, pre-filled prompt
205+zut -p "list all go files" # print final text, exit
206+zut -p --stats stats.json "task" # print final text and write generation stats
207+zut --json "refactor main.go" # newline-delimited json events, exit
208+zut --continue # resume the most recent session for this cwd
209+zut --resume # pick a session to resume
210+zut --list-models # show supported models
211+zut --help
209212 ```
210213
211214 Print-mode stats contain `provider`, `model`, `prompt_tokens`, `reasoning_tokens`, `generated_output_tokens`, and `elapsed_ms`. Counts cover all model turns triggered by the prompt, including tool loops. Prompt tokens include cache reads and writes. `reasoning_tokens` is `null` when the provider does not report a separate count; in that case `generated_output_tokens` is the provider's total output count and may include reasoning. Elapsed time covers the agent run, not startup and credential resolution. The file is written only after a successful run.
@@ -219,7 +222,7 @@ Print-mode stats contain `provider`, `model`, `prompt_tokens`, `reasoning_tokens
219222 | `--api-key <key>` | Override the API key. |
220223 | `--base-url <url>` | Override the provider base URL (tests, self-hosted). |
221224 | `--insecure` | Skip TLS certificate verification for the explicit `--base-url` endpoint or a `baseUrl` defined for a user model in `models.json` (self-signed local/internal inference servers). Built-in providers, auth, and model discovery keep normal TLS verification. |
222-| `--system-prompt <text>` | Replace the default system prompt for this run (also overrides `$ZOT_HOME/SYSTEM.md`). |
225+| `--system-prompt <text>` | Replace the default system prompt for this run (also overrides `$ZUT_HOME/SYSTEM.md`). |
223226 | `--append-system-prompt <text>` | Append text to the system prompt (repeatable). |
224227 | `--reasoning off\|minimum\|low\|medium\|high\|xhigh\|max` | Set the reasoning level on supported models (default: off). `max` is a separate opt-in tier above `xhigh`. |
225228 | `--stats <path>` | With `-p`/`--print`, write generation statistics as JSON. |
@@ -242,7 +245,7 @@ Print-mode stats contain `provider`, `model`, `prompt_tokens`, `reasoning_tokens
242245 - `read`: read text files, or inline images (PNG, JPEG, GIF, WebP).
243246 - `write`: create or overwrite files, making parent directories as needed.
244247 - `edit`: one or more exact-match replacements in an existing file.
245-- `bash`: run a command in the session cwd with merged stdout/stderr and a timeout. On Unix, zot uses `/bin/bash -c` when available, then `bash -c` from `PATH`, and falls back to POSIX `/bin/sh -c` when Bash is unavailable. On Windows, it uses `cmd /C`. macOS ships Bash 3.2 by default, so newer Bash features may be unavailable.
248+- `bash`: run a command in the session cwd with merged stdout/stderr and a timeout. On Unix, zut uses `/bin/bash -c` when available, then `bash -c` from `PATH`, and falls back to POSIX `/bin/sh -c` when Bash is unavailable. On Windows, it uses `cmd /C`. macOS ships Bash 3.2 by default, so newer Bash features may be unavailable.
246249 - `lsp`: query configured language servers and linters for diagnostics, definitions, references, hover information, symbols, renames, code actions, capabilities, and raw protocol requests. LSP servers use stdio JSON-RPC; project `lsp.json` files can add or override servers and CLI linters. Diagnostics are bounded, sorted, deduplicated by path/severity/code/start position, and repeated issues are grouped before they reach the model. See [docs/lsp.md](docs/lsp.md).
247250
248251 When the sandbox is on (see `/jail`), filesystem tools and LSP workspace edits refuse paths outside the session cwd.
@@ -250,30 +253,30 @@ When the sandbox is on (see `/jail`), filesystem tools and LSP workspace edits r
250253 ## Modes
251254
252255 - **Interactive** (default): chat TUI with streaming output, spinner, cost meter, slash commands.
253-- **Print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.
254-- **Stream**: `zot --stream "prompt"` runs without the TUI and writes assistant text to stdout as it arrives. Tool activity goes to stderr.
255-- **JSON**: `zot --json "prompt"` emits one JSON object per agent event to stdout, newline-delimited. The schema is documented in [docs/rpc.md](docs/rpc.md).
256-- **RPC**: `zot rpc` runs as a long-lived child process; commands in on stdin, events and responses out on stdout, both as NDJSON. Designed for embedding zot in third-party apps written in any language. See [docs/rpc.md](docs/rpc.md) for the wire schema and `examples/rpc/{python,node,shell,go}` for working clients.
256+- **Print**: `zut -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.
257+- **Stream**: `zut --stream "prompt"` runs without the TUI and writes assistant text to stdout as it arrives. Tool activity goes to stderr.
258+- **JSON**: `zut --json "prompt"` emits one JSON object per agent event to stdout, newline-delimited. The schema is documented in [docs/rpc.md](docs/rpc.md).
259+- **RPC**: `zut rpc` runs as a long-lived child process; commands in on stdin, events and responses out on stdout, both as NDJSON. Designed for embedding zut in third-party apps written in any language. See [docs/rpc.md](docs/rpc.md) for the wire schema and `examples/rpc/{python,node,shell,go}` for working clients.
257260
258-## zotfile agents
261+## zutfile agents
259262
260-A zotfile packages an agent's instructions, skills, requirements, and enforced tool permissions as a shareable agent. Run one from a local directory, a packed `.zot` artifact, a short name, or directly from a public GitHub repository:
263+A zutfile packages an agent's instructions, skills, requirements, and enforced tool permissions as a shareable agent. Run one from a local directory, a packed `.zut` artifact, a short name, or directly from a public GitHub repository:
261264
262265 ```bash
263-zot run ./my-agent
264-zot run ./my-agent.zot
265-zot run frkr/zot-archify
266-zot run acme/agents/code-reviewer --cwd /path/to/project
266+zut run ./my-agent
267+zut run ./my-agent.zut
268+zut run frkr/zot-archify
269+zut run acme/agents/code-reviewer --cwd /path/to/project
267270 ```
268271
269-Single-part names resolve only to a matching local directory or `.zot` archive. Anyone can publish an agent as a public GitHub repository and run it as `owner/repository`, or publish a collection and run an agent directory as `owner/repository/agent`. zot has no built-in owner, official collection, configured registry, or allowlist. For GitHub sources, zot downloads the repository archive into a temporary directory, validates and runs the selected agent, then removes the downloaded source when the command exits. Agent data, consent receipts, and sessions still persist under `$ZOT_HOME`. See [docs/zotfiles.md](docs/zotfiles.md) for authoring, permissions, packaging, and current limitations.
272+Single-part names resolve only to a matching local directory or `.zut` archive. Anyone can publish an agent as a public GitHub repository and run it as `owner/repository`, or publish a collection and run an agent directory as `owner/repository/agent`. zut has no built-in owner, official collection, configured registry, or allowlist. For GitHub sources, zut downloads the repository archive into a temporary directory, validates and runs the selected agent, then removes the downloaded source when the command exits. Agent data, consent receipts, and sessions still persist under `$ZUT_HOME`. See [docs/zutfiles.md](docs/zutfiles.md) for authoring, permissions, packaging, and current limitations.
270273
271274 ## Embedding
272275
273-Two ways to drive zot from another program:
276+Two ways to drive zut from another program:
274277
275-- **Go in-process**: import `github.com/patriceckhart/zot/packages/agent/sdk`. One `Runtime` per project; `Prompt(ctx, text, images)` returns a channel of `Event`. Small example in `examples/sdk/`.
276-- **Any language, out-of-process**: spawn `zot rpc` as a subprocess and exchange newline-delimited JSON over its stdin/stdout. Wire format and event schema in [docs/rpc.md](docs/rpc.md). Reference clients live under `examples/rpc/`.
278+- **Go in-process**: import `github.com/bnema/zut/packages/agent/sdk`. One `Runtime` per project; `Prompt(ctx, text, images)` returns a channel of `Event`. Small example in `examples/sdk/`.
279+- **Any language, out-of-process**: spawn `zut rpc` as a subprocess and exchange newline-delimited JSON over its stdin/stdout. Wire format and event schema in [docs/rpc.md](docs/rpc.md). Reference clients live under `examples/rpc/`.
277280
278281 Both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.
279282
@@ -285,26 +288,26 @@ Slash command names are case-insensitive in the TUI and messaging backends; argu
285288 |---|---|
286289 | `/help` | Show key bindings and commands. |
287290 | `/login` | Log in via API key or subscription (opens a dialog). |
288-| `/logout [provider]` | Clear credentials for any logged-in provider, or all when omitted. `/logout openai-codex` clears ChatGPT/Codex subscription auth while preserving a public OpenAI API key; `/logout kimi` also disables fallback to the official Kimi Code CLI token until you log in to Kimi through zot again. |
291+| `/logout [provider]` | Clear credentials for any logged-in provider, or all when omitted. `/logout openai-codex` clears ChatGPT/Codex subscription auth while preserving a public OpenAI API key; `/logout kimi` also disables fallback to the official Kimi Code CLI token until you log in to Kimi through zut again. |
289292 | `/model` | Pick a model from a list (or `/model <id>` to set directly). |
290293 | `/reasoning` | Set the reasoning level for subsequent model calls. |
291294 | `/llama` | Connect to the configured llama.cpp router, load, unload, or remove cached models, and search/download GGUF models from Hugging Face with live progress. Shown after llama.cpp login is configured. |
292295 | `/sessions` | Resume a previous session for this directory. The picker omits branches created only for tree navigation. |
293296 | `/fork` | Pick a previous user message and fork the current session after that turn. The selected turn becomes branch context and no provider turn starts during the checkout. |
294-| `/session` | Four ops on the current session: `export` to a portable `.zotsession` file, `import` one back in, `fork` from a past user message into a new branch, `tree` to switch between branches. Opens a picker without an argument; direct forms: `/session export [path]`, `/session import <path>`, `/session fork`, `/session tree`. Default export destination is `~/Downloads`. `Esc` `Esc` from an idle, empty editor is a shortcut for `/session tree` when the terminal supplies two parsed bare Escape events. |
297+| `/session` | Four ops on the current session: `export` to a portable `.zutsession` file, `import` one back in, `fork` from a past user message into a new branch, `tree` to switch between branches. Opens a picker without an argument; direct forms: `/session export [path]`, `/session import <path>`, `/session fork`, `/session tree`. Default export destination is `~/Downloads`. `Esc` `Esc` from an idle, empty editor is a shortcut for `/session tree` when the terminal supplies two parsed bare Escape events. |
295298 | `/jump` | Scroll the chat to a previous turn (or `/jump <text>` to filter). |
296299 | `/btw` | Side chat with full context that doesn't add to the main thread. |
297300 | `/subagents` | Spawn, monitor, and chat with background subagents. Each is a separate long-lived process with bounded lifecycle state, structured results, and optional Git worktree isolation. Named profiles can be selected with `agent`; see [docs/subagents.md](docs/subagents.md). |
298301 | `/skills` | List discovered skills (SKILL.md files) and preview their bodies. |
299302 | `/compact` | Summarize the transcript into one message to free up context. |
300-| `/study` | Run the canned prompt "Read and understand everything in the current directory." so the agent has full project context before you start asking targeted questions. Pass a path — typed, drag-dropped, or selected via `@` — to target a specific file or directory instead: `/study [dir:packages/]`, `/study cmd/zot/main.go`. |
303+| `/study` | Run the canned prompt "Read and understand everything in the current directory." so the agent has full project context before you start asking targeted questions. Pass a path — typed, drag-dropped, or selected via `@` — to target a specific file or directory instead: `/study [dir:packages/]`, `/study cmd/zut/main.go`. |
301304 | `/jail` | Confine tools to the current directory. |
302305 | `/unjail` | Allow tools to touch paths outside again. |
303306 | `/reload-ext` | Hot-reload all extensions (re-read manifests, respawn subprocesses, rebuild tool registry). |
304307 | `/telegram` | Connect, disconnect, or show status of the Telegram bridge (takes `connect` / `disconnect` / `status` as an optional argument; opens a picker without one). When connected, DMs from the paired user become prompts in the running session and the assistant's replies are mirrored back to Telegram. Alias: `/tg`. |
305-| `/settings` | Change persistent settings, including inline images, terminal alerts, AI terminal titles, auto-subagents, Ponytail coding mode, fast mode, main/sub-agent LSP access, and the auto-compact threshold. Saved to `$ZOT_HOME/config.json`; setting changes apply without a restart, while AI title generation waits for the first real prompt. |
308+| `/settings` | Change persistent settings, including inline images, terminal alerts, AI terminal titles, auto-subagents, Ponytail coding mode, fast mode, main/sub-agent LSP access, and the auto-compact threshold. Saved to `$ZUT_HOME/config.json`; setting changes apply without a restart, while AI title generation waits for the first real prompt. |
306309 | `/clear` | Clear the chat transcript. |
307-| `/exit` | Exit zot. |
310+| `/exit` | Exit zut. |
308311
309312 Extension-registered commands appear under a divider at the bottom of the popup, sorted by name.
310313
@@ -314,17 +317,17 @@ Type `!` followed by a command to run it directly without going through the mode
314317
315318 ### `/sessions`
316319
317-Shows previous sessions for the current working directory, newest first, with timestamp, model, message count, cost, and the session title. Session entries load in the background and are revealed incrementally in newest-first order; an older result may wait briefly for entries ahead of it so the list never reorders, while the picker keeps showing progress for the remaining entries. Once an entry appears, `up`/`down` and `enter` can select it immediately; `esc` cancels and closes the picker. zot swaps the current session file for the selected one and replays the full transcript (including tool calls) into the agent. Sessions remember their stored provider and model: resuming rebuilds the live agent when necessary instead of silently using a different model; if that rebuild fails, the current session is left unchanged. Branches created for tree navigation remain available to `/session tree` but are hidden from this flat picker. A fresh interactive session asks the active model for a concise title after the first real prompt and restores that title when resumed.
320+Shows previous sessions for the current working directory, newest first, with timestamp, model, message count, cost, and the session title. Session entries load in the background and are revealed incrementally in newest-first order; an older result may wait briefly for entries ahead of it so the list never reorders, while the picker keeps showing progress for the remaining entries. Once an entry appears, `up`/`down` and `enter` can select it immediately; `esc` cancels and closes the picker. zut swaps the current session file for the selected one and replays the full transcript (including tool calls) into the agent. Sessions remember their stored provider and model: resuming rebuilds the live agent when necessary instead of silently using a different model; if that rebuild fails, the current session is left unchanged. Branches created for tree navigation remain available to `/session tree` but are hidden from this flat picker. A fresh interactive session asks the active model for a concise title after the first real prompt and restores that title when resumed.
318321
319322 ### `/session`
320323
321324 Four ops on the current session. `/session` alone opens a picker; each is also runnable directly.
322325
323-- **`/session export [path]`**. Writes the running transcript to a portable `.zotsession` file. Default destination is `~/Downloads/<timestamp>-<session-id>-<prompt-slug>.zotsession`. Pass a path to override; a directory is fine (a dated name is built inside), a bare name gets `.zotsession` appended. The meta's cwd is stripped on the way out so the recipient doesn't see your filesystem layout.
326+- **`/session export [path]`**. Writes the running transcript to a portable `.zutsession` file. Default destination is `~/Downloads/<timestamp>-<session-id>-<prompt-slug>.zutsession`. Pass a path to override; a directory is fine (a dated name is built inside), a bare name gets `.zutsession` appended. The meta's cwd is stripped on the way out so the recipient doesn't see your filesystem layout.
324327
325- **What's included.** Only the main chat thread of the running session — messages, tool calls, tool results, compactions, and usage. **`/subagents` subagents are NOT included.** Their transcripts, unix-socket inboxes, and per-agent session files are all machine-local; a `.zotsession` is just a chat transcript and has no way to revive a unix socket on another box. If you want the conversation, copy it out of the dashboard manually.
326-- **`/session import <path>`**. Copies a `.zotsession` file into `$ZOT_HOME/sessions/<cwd-hash>/` with a fresh id and the current cwd, then switches the running agent onto it. Imported sessions are first-class: they show up in `/sessions` and `/jump`, and in the current-family tree when they are part of that family. Drag-drop paths in the editor are accepted (zot strips the surrounding quotes automatically).
327-- **`/fork`** (also available as **`/session fork`**). Opens a turn picker (same shape as `/jump`). Pick any past user message; zot copies the transcript through that turn into a new branch and switches to it without starting a provider request. The parent session stays on disk. Use it to try a different question without polluting the original transcript, or to rewind after the agent went down the wrong path. Use a user-message row in `/session tree` when you want the prompt restored for editing, including image attachments.
328+ **What's included.** Only the main chat thread of the running session — messages, tool calls, tool results, compactions, and usage. **`/subagents` subagents are NOT included.** Their transcripts, unix-socket inboxes, and per-agent session files are all machine-local; a `.zutsession` is just a chat transcript and has no way to revive a unix socket on another box. If you want the conversation, copy it out of the dashboard manually.
329+- **`/session import <path>`**. Copies a `.zutsession` file into `$ZUT_HOME/sessions/<cwd-hash>/` with a fresh id and the current cwd, then switches the running agent onto it. Imported sessions are first-class: they show up in `/sessions` and `/jump`, and in the current-family tree when they are part of that family. Drag-drop paths in the editor are accepted (zut strips the surrounding quotes automatically).
330+- **`/fork`** (also available as **`/session fork`**). Opens a turn picker (same shape as `/jump`). Pick any past user message; zut copies the transcript through that turn into a new branch and switches to it without starting a provider request. The parent session stays on disk. Use it to try a different question without polluting the original transcript, or to rewind after the agent went down the wrong path. Use a user-message row in `/session tree` when you want the prompt restored for editing, including image attachments.
328331 - **`/session tree`**. Shows the current session's family: its root ancestor and descendants, not unrelated root sessions in the same cwd. Use `up`/`down` and `enter` to choose a row; `esc` closes the tree. The picker keeps pre-compaction history available as forkable user and assistant messages, while hiding tool calls, tool results, compaction checkpoints, shell/image helper rows, and other internal context. Message rows are indented at their branch points and the current endpoint is tagged `[current]`. Empty and detached branch points are represented by visible boundary rows. A child whose historical fork point no longer matches after compaction stays discoverable as a detached branch under the parent's current tail rather than disappearing. Parentless sessions are roots; orphaned children (whose parent file was deleted) remain roots.
329332
330333 Selecting a user-message row creates a navigation branch before that message and restores the complete editable draft, including text blocks and image attachments; no provider request is made until you submit it. Selecting an assistant-message row checks out a safe message boundary, and selecting an empty or detached boundary checks out that branch point with an empty editor. Navigation branches are hidden from `/sessions` but remain in the tree.
@@ -333,7 +336,7 @@ Four ops on the current session. `/session` alone opens a picker; each is also r
333336
334337 ### `/jump`
335338
336-Opens a turn picker for the current session, one row per user prompt, each showing the turn number, how many tools that turn invoked, and the first line of the prompt. `up`/`down` to pick, `enter` to jump, `esc` to cancel. Any printable rune while the picker is open extends a filter; backspace narrows it back. `/jump <text>` pre-applies the filter; if exactly one turn matches, zot jumps straight there without showing the picker.
339+Opens a turn picker for the current session, one row per user prompt, each showing the turn number, how many tools that turn invoked, and the first line of the prompt. `up`/`down` to pick, `enter` to jump, `esc` to cancel. Any printable rune while the picker is open extends a filter; backspace narrows it back. `/jump <text>` pre-applies the filter; if exactly one turn matches, zut jumps straight there without showing the picker.
337340
338341 Jumping is non-destructive. The transcript is untouched, the viewport just scrolls so the chosen turn is at the top. A muted line at the top of the chat reads `viewing turn N of M, pgdn to catch up`. Scroll back to the bottom with `pgdn` (or keep scrolling with the arrow keys) and the indicator goes away.
339342
@@ -352,7 +355,7 @@ Inside the overlay: `enter` sends, `esc` cancels an in-flight call (or closes th
352355
353356 ### `/subagents`
354357
355-Background subagents that run alongside your main session. Each one is a separate `zot` subprocess with its own model loop, persistent session file, versioned JSONL control protocol, structured turn result, and lifecycle state. Shared checkout mode preserves the historical behavior; worktree mode creates a temporary detached Git worktree, captures a patch and changed-file list, and never merges automatically.
358+Background subagents that run alongside your main session. Each one is a separate `zut` subprocess with its own model loop, persistent session file, versioned JSONL control protocol, structured turn result, and lifecycle state. Shared checkout mode preserves the historical behavior; worktree mode creates a temporary detached Git worktree, captures a patch and changed-file list, and never merges automatically.
356359
357360 > **Choose the workspace deliberately.** Use `isolation:"worktree"` for parallel coding so children cannot edit the host checkout. Use shared mode for read-only review or explicitly coordinated work. Worktree isolation is an accident-prevention guardrail, not a security sandbox.
358361
@@ -389,37 +392,37 @@ Background subagents that run alongside your main session. Each one is a separat
389392
390393 **Session scoping** — each agent is stamped with the host session that spawned it and only shows up in that session's dashboard. Swap sessions with `/sessions` and the dashboard re-narrows accordingly. Agents from other sessions keep running in the background and reappear when you switch back.
391394
392-**Persistence across zot restarts** — every spawn writes a durable manifest, append-only event log, session file, and structured `result.json` under `$ZOT_HOME/subagents/agents/<id>/`. On the next `zot` launch they show up as **detached**; use `R` or `/subagents resume-session <id>` to continue the existing session. `/subagents restart-task <id>` is the explicit operation that intentionally replays the stored task.
395+**Persistence across zut restarts** — every spawn writes a durable manifest, append-only event log, session file, and structured `result.json` under `$ZUT_HOME/subagents/agents/<id>/`. On the next `zut` launch they show up as **detached**; use `R` or `/subagents resume-session <id>` to continue the existing session. `/subagents restart-task <id>` is the explicit operation that intentionally replays the stored task.
393396
394-**Where state lives** — per-agent manifests, session files, events, results, and patches live under `$ZOT_HOME/subagents/agents/<id>/`; inbox sockets are runtime-only and permission-restricted. Shared-mode edits land directly in the repo. Worktree-mode edits are captured as durable patches and changed-file summaries before cleanup.
397+**Where state lives** — per-agent manifests, session files, events, results, and patches live under `$ZUT_HOME/subagents/agents/<id>/`; inbox sockets are runtime-only and permission-restricted. Shared-mode edits land directly in the repo. Worktree-mode edits are captured as durable patches and changed-file summaries before cleanup.
395398
396-**`/session export` does NOT bundle subagents.** A `.zotsession` is just the main chat transcript; per-agent state (session file, unix-socket inbox) is machine-local and doesn't round-trip through a JSONL file. To share what an agent said, copy it out of the transcript view manually.
399+**`/session export` does NOT bundle subagents.** A `.zutsession` is just the main chat transcript; per-agent state (session file, unix-socket inbox) is machine-local and doesn't round-trip through a JSONL file. To share what an agent said, copy it out of the transcript view manually.
397400
398401 **Auto-subagents.** With `/settings` -> auto-subagents on, the main agent gets the canonical `subagent_spawn` tool and a system-prompt nudge to delegate independent work. The companion `subagent_status` tool accepts an optional `agent_id`: omit it to list visible workers, or pass a full id / unique prefix to query one without waiting for completion. It returns privacy-safe JSON metadata with the worker id, lifecycle state (`starting`, `running`, `completed`, `failed`, `cancelled`, or `detached`), underlying process/turn state, timestamps, a bounded first-line task summary, and terminal-result metadata when available; it does not return prompts, transcripts, result output, credentials, provider settings, or filesystem paths. The spawn tool returns the child id and initial state immediately; completion is a durable `turn.result` with a `subagent://<id>/result` reference. It accepts named profiles, timeout, `max_turns`, and `isolation:"shared"` or `isolation:"worktree"`. Omit `max_turns` to use the supervisor policy; the default ceiling is 3 and `subagents.max_turns` changes it. Global concurrency and total-spawn budgets apply equally to slash commands, tool calls, and batches. Off by default; toggle from `/settings`.
399402
400403 ### Named subagent profiles
401404
402-zot discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default. With auto-subagents enabled, the main agent receives `[subagents_list]` metadata and can select a named profile through `subagent_spawn`. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples.
405+zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default. With auto-subagents enabled, the main agent receives `[subagents_list]` metadata and can select a named profile through `subagent_spawn`. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples.
403406
404407 ### `/settings`
405408
406-Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` or `space` to change the selected row, `esc` to close (rows that open a sub-view, like model shortcuts, use `esc` to go back one level first). Changes are written to `$ZOT_HOME/config.json` without a restart; individual settings document whether they affect the current turn, the next model call, or a later session event. Current settings:
409+Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` or `space` to change the selected row, `esc` to close (rows that open a sub-view, like model shortcuts, use `esc` to go back one level first). Changes are written to `$ZUT_HOME/config.json` without a restart; individual settings document whether they affect the current turn, the next model call, or a later session event. Current settings:
407410
408411 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.
409412 - **terminal alerts** — emit a terminal bell when the main session stops with work that may need attention, or when an extension raises a structured alert. Enabled by default; changes apply immediately and persist as `terminal_alerts_enabled`. Terminal emulators may render the bell audibly, visually, or not at all.
410-- **AI terminal titles** — after the first real prompt of a fresh interactive session, make one small hidden request to the active model and set the terminal title to `zot: <title>`. The title is limited to 40 Unicode characters, persisted with the session, restored on resume, and never added to the conversation. Enabled by default; disable it to avoid the extra model request. The toggle applies immediately, but title generation still waits for the first real prompt and never starts from startup context, resumed history, or slash commands. Persists as `terminal_title_enabled`.
413+- **AI terminal titles** — after the first real prompt of a fresh interactive session, make one small hidden request to the active model and set the terminal title to `zut: <title>`. The title is limited to 40 Unicode characters, persisted with the session, restored on resume, and never added to the conversation. Enabled by default; disable it to avoid the extra model request. The toggle applies immediately, but title generation still waits for the first real prompt and never starts from startup context, resumed history, or slash commands. Persists as `terminal_title_enabled`.
411414 - **auto-subagents** — let the main agent spawn background sub-agents in parallel via `subagent_spawn` and query live state via `subagent_status`. Off by default. The tools accept named profiles, reasoning, timeout, turn limits, and shared/worktree isolation where applicable; lifecycle and result references are persisted. Completion updates still arrive through the auto-subagents watcher. See `/subagents` and [docs/subagents.md](docs/subagents.md) for details.
412-- **Ponytail coding mode** — include compact engineering guidance in each resolved system prompt; the guidance tells the model to apply it to coding, debugging, and review work rather than ordinary conversation. It favors understanding the real flow, small validated changes, reuse, and preserving safety checks. Enabled by default; changes apply to the next model call and persist as `ponytail_enabled` in `$ZOT_HOME/config.json`. It is included by interactive, print, stream, JSON, RPC, subagent, bot, SDK, and Zotfile agents.
415+- **Ponytail coding mode** — include compact engineering guidance in each resolved system prompt; the guidance tells the model to apply it to coding, debugging, and review work rather than ordinary conversation. It favors understanding the real flow, small validated changes, reuse, and preserving safety checks. Enabled by default; changes apply to the next model call and persist as `ponytail_enabled` in `$ZUT_HOME/config.json`. It is included by interactive, print, stream, JSON, RPC, subagent, bot, SDK, and Zutfile agents.
413416 - **fast mode** — request OpenAI's Fast service tier for OpenAI, OpenAI Responses, and OpenAI Codex models. Off by default; changes apply on the next model call and persist as `fast_mode`. Other providers return an unsupported-provider error. Fast mode may cost more and depends on the selected OpenAI model/account.
414417 - **lsp in main session** — enable the built-in `lsp` tool and code diagnostics for the main agent. Enabled by default; changes persist as `lsp_enabled`.
415418 - **lsp in sub-agents** — allow newly spawned background sub-agents to use the built-in `lsp` tool. Enabled by default; changes persist as `subagent_lsp_enabled` and apply when a child starts.
416-- **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. After a successful threshold compaction, zot automatically resumes an unfinished most-recent intent; a prompt queued during compaction takes priority. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from context-window and payload-too-large responses.
419+- **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. After a successful threshold compaction, zut automatically resumes an unfinished most-recent intent; a prompt queued during compaction takes priority. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from context-window and payload-too-large responses.
417420 - **jail new sessions by default**: start every new agent with tools confined to its working directory. Off by default. The setting applies to interactive, print, JSON, RPC, and background-agent runs, persists as `jail_by_default`, and immediately updates the current interactive session. `/jail` and `/unjail` remain session-scoped overrides and do not change this default.
418421 - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`.
419-- **show loaded resources at startup**: list the active Zotfile agent (for `zot run`), loaded `AGENTS.md` paths, extensions, and user-installed skills in compact sections above the transcript. Built-in skills are omitted. Off by default. Changes apply immediately and persist to `config.json` as `show_instructions_at_startup`.
422+- **show loaded resources at startup**: list the active Zutfile agent (for `zut run`), loaded `AGENTS.md` paths, extensions, and user-installed skills in compact sections above the transcript. Built-in skills are omitted. Off by default. Changes apply immediately and persist to `config.json` as `show_instructions_at_startup`.
420423 - **TUI settings**: opens a sub-view for input layout and status placement. **Input style** can be `plain` (default prompt line), `lines` (separator lines above and below the input), or `block` (a user-bubble-style input block). **Status position** places model, usage, and working-directory information above or below the input. **Working spinner position** places the busy spinner above or below the input. Changes apply immediately and persist to `config.json` as `tui_input_style`, `tui_status_position`, and `tui_working_position` (`above_input` or `below_input` for the position fields).
421424 - **reasoning level**: choose reasoning for supported models: off, minimum, low, medium, high, xhigh, or max. The `max` tier is opt-in and sent natively to GPT-5.6 and adaptive-thinking Claude models; unsupported backends clamp it to their highest accepted effort. The change is persisted to `config.json` and applied to the next model call. Use `/reasoning` to open this selector directly. The selector only shows distinct levels supported by the active model; models without reasoning support only offer `off`.
422-- **color theme** — choose the built-in auto, inherited (from terminal), dark, or light theme, or any JSON theme discovered under `$ZOT_HOME/themes` or a loaded extension. Built-in and JSON themes follow terminal color capability: truecolor terminals receive RGB output, while RGB values fall back to xterm-256 elsewhere. Theme files can use xterm-256, ANSI, or RGB values for semantic UI colors and can also override syntax colors and spinner frames/messages. Changes apply immediately; if a selected theme file is deleted, zot resets to auto. See [docs/themes.md](docs/themes.md).
425+- **color theme** — choose the built-in auto, inherited (from terminal), dark, or light theme, or any JSON theme discovered under `$ZUT_HOME/themes` or a loaded extension. Built-in and JSON themes follow terminal color capability: truecolor terminals receive RGB output, while RGB values fall back to xterm-256 elsewhere. Theme files can use xterm-256, ANSI, or RGB values for semantic UI colors and can also override syntax colors and spinner frames/messages. Changes apply immediately; if a selected theme file is deleted, zut resets to auto. See [docs/themes.md](docs/themes.md).
423426 - **model shortcuts** — opens a sub-view with nine slots (`model 1` ... `model 9`). `enter` on a slot opens the same `/model` selector and binds the chosen provider/model to that slot; `backspace` clears a slot. Once assigned, press `Ctrl+1` ... `Ctrl+9` from the editor to switch the active model instantly (the same cross-provider swap `/model` performs, transcript and cost carried over). Assigning a shortcut does not change the current model. Shortcuts are skipped while a turn is running.
424427
425428 ### `/skills`
@@ -430,21 +433,21 @@ Opens a picker listing every discovered SKILL.md file, built-ins hidden. Each ro
430433
431434 Sends the current transcript through the model with a structured summarization prompt. The returned summary replaces the transcript as one synthetic user message, with the last few exchanges kept verbatim for continuity. The status bar's context meter resets. Use it when the context meter creeps past ~80%.
432435
433-zot also auto-compacts in the background: after any turn that reaches the configured context threshold, the agent kicks off a condense pass on its own and, once it succeeds, resumes an unfinished most-recent intent automatically. Completed text answers settle normally, while a prompt queued during compaction runs instead of the automatic continuation. Choose `off`, `70%`, `80%`, `85%` (default), or `90%` under `/settings` → **auto-compact threshold**. You'll see `condensing history, esc to cancel` above the status bar and an `(auto)` tag next to the context percentage; `esc` aborts it without touching the transcript. Turning the percentage trigger off does not disable automatic compaction and retry after a context-window or payload-too-large response.
436+zut also auto-compacts in the background: after any turn that reaches the configured context threshold, the agent kicks off a condense pass on its own and, once it succeeds, resumes an unfinished most-recent intent automatically. Completed text answers settle normally, while a prompt queued during compaction runs instead of the automatic continuation. Choose `off`, `70%`, `80%`, `85%` (default), or `90%` under `/settings` → **auto-compact threshold**. You'll see `condensing history, esc to cancel` above the status bar and an `(auto)` tag next to the context percentage; `esc` aborts it without touching the transcript. Turning the percentage trigger off does not disable automatic compaction and retry after a context-window or payload-too-large response.
434437
435438 ### `/jail`
436439
437440 Enforces a sandbox rooted at the cwd shown in the status bar. `read`, `write`, and `edit` resolve their target path (including through symlinks) and refuse anything outside the sandbox. `bash` refuses obvious escape patterns (`sudo`, `rm -rf /`, leading `cd /`, `cd ..`, `cd ~`, `chmod -R`, `dd of=/`, and similar) and rejects shell arguments or redirections that point outside the sandbox. The status bar shows `jailed, ~/your/cwd` while active. Enable **jail new sessions by default** in `/settings` to persist this behavior across launches; `/unjail` then unlocks only the current session.
438441
439-This is a guardrail against accidents, not a hard security boundary. If you need real isolation, run zot under docker or a proper sandbox.
442+This is a guardrail against accidents, not a hard security boundary. If you need real isolation, run zut under docker or a proper sandbox.
440443
441444 ## Sessions
442445
443-Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZOT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Session metadata stores the provider and model used by that session; resume honors that pair, rebuilding the active agent when it differs and refusing the switch without discarding the current session if rebuilding fails. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy.
446+Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZUT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Session metadata stores the provider and model used by that session; resume honors that pair, rebuilding the active agent when it differs and refusing the switch without discarding the current session if rebuilding fails. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy.
444447
445448 ## Providers
446449
447-zot's built-in provider catalog includes:
450+zut's built-in provider catalog includes:
448451
449452 - **Subscription-capable**: Anthropic Claude Pro/Max (`anthropic`), OpenAI Codex / ChatGPT Plus/Pro (`openai-codex`), Kimi Code (`kimi`), SuperGrok/X Premium (`xai`), GitHub Copilot (`github-copilot`).
450453 - **Direct API providers**: Anthropic, OpenAI Chat Completions, OpenAI Responses, DeepSeek, Google Gemini, Kimi/Moonshot, Moonshot CN, Groq, Cerebras, xAI, Together AI, Hugging Face Router, OpenRouter, Mistral, Z.AI, Xiaomi/MiMo token-plan regions, MiniMax global/CN, Fireworks, Vercel AI Gateway, OpenCode/OpenCode Go.
@@ -457,15 +460,15 @@ Use `/login` to store API keys or subscription credentials. `/model` only shows
457460
458461 `--list-models` or the `/model` picker shows the full catalog across all built-in providers. Three sources:
459462
460-- **Catalog**: models baked into zot, covering Claude, GPT/Codex, Gemini/Gemma, Kimi/Moonshot, DeepSeek, Groq-hosted Llama/Gemma/Compound, OpenRouter-routed models, Bedrock model ids, Vertex model ids, Azure OpenAI deployments, Copilot models, and other provider-specific catalog entries.
461-- **Live**: IDs discovered from `GET /v1/models` using your stored API key (cached for 6h in `$ZOT_HOME/models-cache.json`, refreshed in the background on startup).
463+- **Catalog**: models baked into zut, covering Claude, GPT/Codex, Gemini/Gemma, Kimi/Moonshot, DeepSeek, Groq-hosted Llama/Gemma/Compound, OpenRouter-routed models, Bedrock model ids, Vertex model ids, Azure OpenAI deployments, Copilot models, and other provider-specific catalog entries.
464+- **Live**: IDs discovered from `GET /v1/models` using your stored API key (cached for 6h in `$ZUT_HOME/models-cache.json`, refreshed in the background on startup).
462465 - **Speculative**: IDs that appear in the upstream generator but aren't live on the public API yet. They'll 404 today and start working the moment the provider ships them.
463466
464467 The context meter in the status line uses the model's advertised context window to show how much of it your last turn consumed.
465468
466469 ### Model fallback (rescue)
467470
468-When a turn fails because of a recoverable provider error — expired token (`401`), permission denied (`403`), rate limit (`429`), provider outage (`502`/`503`/`504`), or a transient network failure — zot opens a **rescue** picker over the chat instead of just painting a red banner.
471+When a turn fails because of a recoverable provider error — expired token (`401`), permission denied (`403`), rate limit (`429`), provider outage (`502`/`503`/`504`), or a transient network failure — zut opens a **rescue** picker over the chat instead of just painting a red banner.
469472
470473 The picker is the same vertical list / fuzzy filter UI as `/model`, but it only shows models from providers you're currently logged in to (env vars, `auth.json`, Kimi CLI fallback, ollama). The failed model is excluded. Press `↑`/`↓` to choose, `enter` to retry the **same prompt** on the new model, `esc` to dismiss.
471474
@@ -477,7 +480,7 @@ No configuration is required — the candidate list is built dynamically from yo
477480
478481 ### Custom models
479482
480-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:
483+Place a `models.json` in `$ZUT_HOME` (`$XDG_STATE_HOME/zut/` when set, otherwise the platform default above) to add models that aren't in the baked-in catalog or to override existing entries:
481484
482485 ```json
483486 {
@@ -526,7 +529,7 @@ A top-level provider key that is not a built-in id defines a custom provider. Gi
526529
527530 Custom providers are first-class: they appear in `--list-models`, `/model`, and `/login`. `models.json` never stores secrets. Supply the key through `/login`, `--api-key`, or a derived environment variable in upper snake case (so `my-company` reads `MY_COMPANY_API_KEY`). Because many self-hosted gateways do not expose a model-list endpoint, custom provider keys are accepted and stored without a verification probe; an invalid key surfaces on the first model call.
528531
529-To retrieve this custom provider's key from a password manager, add a matching entry to `$ZOT_HOME/auth.json`:
532+To retrieve this custom provider's key from a password manager, add a matching entry to `$ZUT_HOME/auth.json`:
530533
531534 ```json
532535 {
@@ -545,15 +548,15 @@ To retrieve this custom provider's key from a password manager, add a matching e
545548 The provider IDs in `models.json` and `auth.json` must match. Then select the custom model directly:
546549
547550 ```bash
548-zot --provider my-company --model company-llm-v2
551+zut --provider my-company --model company-llm-v2
549552 ```
550553
551554 ### Kimi Code
552555
553-zot has built-in Kimi support through the Kimi Coding endpoint and Moonshot's OpenAI-compatible chat API.
556+zut has built-in Kimi support through the Kimi Coding endpoint and Moonshot's OpenAI-compatible chat API.
554557
555558 ```bash
556-zot --provider kimi
559+zut --provider kimi
557560 ```
558561
559562 By default this uses:
@@ -566,15 +569,15 @@ Credential lookup order for Kimi:
566569 1. `--api-key`
567570 2. `KIMI_API_KEY`
568571 3. `MOONSHOT_API_KEY`
569-4. `$ZOT_HOME/auth.json`
572+4. `$ZUT_HOME/auth.json`
570573 5. the official Kimi Code CLI token at `~/.kimi/credentials/kimi-code.json`, unless disabled by `/logout kimi`
571574
572-Use `/login` for either API-key login or Kimi Code subscription login. The subscription flow uses Kimi Code's device-code OAuth flow: zot opens the verification URL, waits for browser approval, stores the token in `auth.json`, and refreshes it automatically.
575+Use `/login` for either API-key login or Kimi Code subscription login. The subscription flow uses Kimi Code's device-code OAuth flow: zut opens the verification URL, waits for browser approval, stores the token in `auth.json`, and refreshes it automatically.
573576
574577 For direct Moonshot API keys or a custom compatible endpoint:
575578
576579 ```bash
577-zot --provider kimi --model kimi-k2-0905-preview --base-url https://api.moonshot.ai/v1 --api-key "$KIMI_API_KEY"
580+zut --provider kimi --model kimi-k2-0905-preview --base-url https://api.moonshot.ai/v1 --api-key "$KIMI_API_KEY"
578581 ```
579582
580583 Kimi K3 is built in as `kimi/k3`, `moonshotai/kimi-k3`, `moonshotai-cn/kimi-k3`, `opencode-go/kimi-k3`, `openrouter/moonshotai/kimi-k3`, and `vercel-ai-gateway/moonshotai/kimi-k3`. Its output limit is 131,072 tokens on every built-in route.
@@ -587,10 +590,10 @@ xAI supports either `XAI_API_KEY` or `/login` subscription authentication. The s
587590
588591 ### DeepSeek
589592
590-zot has built-in DeepSeek support through DeepSeek's OpenAI-compatible chat API.
593+zut has built-in DeepSeek support through DeepSeek's OpenAI-compatible chat API.
591594
592595 ```bash
593-zot --provider deepseek
596+zut --provider deepseek
594597 ```
595598
596599 By default this uses:
@@ -604,26 +607,26 @@ Credential lookup order for DeepSeek:
604607
605608 1. `--api-key`
606609 2. `DEEPSEEK_API_KEY`
607-3. `$ZOT_HOME/auth.json`
610+3. `$ZUT_HOME/auth.json`
608611
609-Use `/login` and pick **api key** to paste a DeepSeek key. zot probes `/v1/models` once and stores the key under `deepseek` in `auth.json`.
612+Use `/login` and pick **api key** to paste a DeepSeek key. zut probes `/v1/models` once and stores the key under `deepseek` in `auth.json`.
610613
611614 > **Auth model: API key only.** DeepSeek does not offer a subscription OAuth flow. The `/login subscription` step lists only Anthropic, OpenAI, and Kimi; DeepSeek shows up only under `/login → api key`.
612615
613-> **Text only at the wire level.** DeepSeek's chat-completions endpoint currently rejects the multimodal content schema (`unknown variant image_url, expected text`). When the active provider is `deepseek`, zot silently drops `ImageBlock` parts from outgoing user/tool messages and keeps only the text. Switching back to a vision-capable model (Claude, GPT-4o/5, Gemini) re-sends the image normally because the session file still stores it.
616+> **Text only at the wire level.** DeepSeek's chat-completions endpoint currently rejects the multimodal content schema (`unknown variant image_url, expected text`). When the active provider is `deepseek`, zut silently drops `ImageBlock` parts from outgoing user/tool messages and keeps only the text. Switching back to a vision-capable model (Claude, GPT-4o/5, Gemini) re-sends the image normally because the session file still stores it.
614617
615618 For a custom-compatible endpoint (mirror, gateway, self-host):
616619
617620 ```bash
618-zot --provider deepseek --base-url https://my-deepseek-mirror.example.com/v1 --api-key "$DEEPSEEK_API_KEY"
621+zut --provider deepseek --base-url https://my-deepseek-mirror.example.com/v1 --api-key "$DEEPSEEK_API_KEY"
619622 ```
620623
621624 ### Google Gemini
622625
623-zot has built-in Google Gemini support through the [AI Studio Generative Language API](https://aistudio.google.com/).
626+zut has built-in Google Gemini support through the [AI Studio Generative Language API](https://aistudio.google.com/).
624627
625628 ```bash
626-zot --provider google
629+zut --provider google
627630 ```
628631
629632 By default this uses:
@@ -638,9 +641,9 @@ Credential lookup order for Google:
638641 1. `--api-key`
639642 2. `GEMINI_API_KEY`
640643 3. `GOOGLE_API_KEY`
641-4. `$ZOT_HOME/auth.json`
644+4. `$ZUT_HOME/auth.json`
642645
643-Use `/login` and pick **api key** to paste an AI Studio key. zot probes `/v1beta/models` once and stores the key under `google` in `auth.json`.
646+Use `/login` and pick **api key** to paste an AI Studio key. zut probes `/v1beta/models` once and stores the key under `google` in `auth.json`.
644647
645648 > **Auth model: API key only (this provider).** Google does not issue OAuth tokens for consumer Gemini Advanced / Google One AI Premium subscriptions, so there is no "log in with your Google subscription" flow. The `/login subscription` step quietly downgrades to the api-key form when you pick Google so you don't end up in a dead end. If you need OAuth/service-account auth instead of an API key, use the `google-vertex` provider below.
646649
@@ -652,10 +655,10 @@ You can add additional Gemini model IDs to `models.json` under the `google` prov
652655
653656 ### Gemini Enterprise Agent Platform (formerly Google Vertex AI)
654657
655-zot also has built-in support for [Gemini Enterprise Agent Platform](https://cloud.google.com/products/gemini-enterprise-agent-platform), Google's enterprise/GCP-hosted Gemini endpoint. Unlike the AI Studio `google` provider above, the `google-vertex` provider supports a Google Cloud API key plus `service_account` and `authorized_user` credential files. It does not support consumer Google login or the complete ADC credential chain, such as metadata-server and workload-identity credentials.
658+zut also has built-in support for [Gemini Enterprise Agent Platform](https://cloud.google.com/products/gemini-enterprise-agent-platform), Google's enterprise/GCP-hosted Gemini endpoint. Unlike the AI Studio `google` provider above, the `google-vertex` provider supports a Google Cloud API key plus `service_account` and `authorized_user` credential files. It does not support consumer Google login or the complete ADC credential chain, such as metadata-server and workload-identity credentials.
656659
657660 ```bash
658-zot --provider google-vertex
661+zut --provider google-vertex
659662 ```
660663
661664 Required configuration (env vars, read at construction time):
@@ -667,34 +670,34 @@ Credential lookup order for Vertex:
667670
668671 1. `GOOGLE_CLOUD_API_KEY`: simplest option. An API key created in the GCP console is sent as `x-goog-api-key`, without a token exchange.
669672 2. `GOOGLE_APPLICATION_CREDENTIALS`: path to a supported credential JSON file.
670-3. **Default ADC file**: if neither of the above is set, zot checks the file written by `gcloud auth application-default login`. This is `~/.config/gcloud/application_default_credentials.json` on Unix systems and `%APPDATA%\gcloud\application_default_credentials.json` on Windows.
673+3. **Default ADC file**: if neither of the above is set, zut checks the file written by `gcloud auth application-default login`. This is `~/.config/gcloud/application_default_credentials.json` on Unix systems and `%APPDATA%\gcloud\application_default_credentials.json` on Windows.
671674
672675 Two credential file shapes are supported:
673676
674-- `type: "service_account"`: zot signs a JWT with the private key and exchanges it for a short-lived access token.
675-- `type: "authorized_user"`: zot exchanges the stored client ID, client secret, and refresh token for a short-lived access token.
677+- `type: "service_account"`: zut signs a JWT with the private key and exchanges it for a short-lived access token.
678+- `type: "authorized_user"`: zut exchanges the stored client ID, client secret, and refresh token for a short-lived access token.
676679
677680 Access tokens are cached in memory and refreshed on demand.
678681
679-If none of these are available, zot errors with `vertex: no auth — set GOOGLE_CLOUD_API_KEY or GOOGLE_APPLICATION_CREDENTIALS`.
682+If none of these are available, zut errors with `vertex: no auth — set GOOGLE_CLOUD_API_KEY or GOOGLE_APPLICATION_CREDENTIALS`.
680683
681684 ### Local models with ollama
682685
683-zot works with [ollama](https://ollama.com) out of the box. Ollama serves an OpenAI-compatible API locally, so any model you have pulled works with zot.
686+zut works with [ollama](https://ollama.com) out of the box. Ollama serves an OpenAI-compatible API locally, so any model you have pulled works with zut.
684687
685688 Quick start:
686689
687690 ```bash
688691 ollama pull qwen3.5:4b
689-zot --provider ollama --model qwen3.5:4b
692+zut --provider ollama --model qwen3.5:4b
690693 ```
691694
692-That's it. No API key needed for local models. zot defaults to `http://localhost:11434`.
695+That's it. No API key needed for local models. zut defaults to `http://localhost:11434`.
693696
694697 For a remote ollama instance or one behind auth:
695698
696699 ```bash
697-zot --provider ollama --model llama3 --base-url https://my-server.com/v1 --api-key my-token
700+zut --provider ollama --model llama3 --base-url https://my-server.com/v1 --api-key my-token
698701 ```
699702
700703 You can also add models to your `models.json` so you don't need flags every time:
@@ -720,7 +723,7 @@ The `ollama` provider uses the OpenAI chat completions protocol internally, so i
720723
721724 ### Local models with llama.cpp router mode
722725
723-zot can connect to a recent [llama.cpp](https://github.com/ggml-org/llama.cpp) router, manage its GGUF files, and use loaded models through the router's OpenAI-compatible inference API. This is separate from Ollama. An Ollama server normally listens on port `11434` and should use zot's `ollama` provider instead.
726+zut can connect to a recent [llama.cpp](https://github.com/ggml-org/llama.cpp) router, manage its GGUF files, and use loaded models through the router's OpenAI-compatible inference API. This is separate from Ollama. An Ollama server normally listens on port `11434` and should use zut's `ollama` provider instead.
724727
725728 Install or update llama.cpp. On macOS with Homebrew:
726729
@@ -730,7 +733,7 @@ brew install llama.cpp
730733 brew upgrade llama.cpp
731734 ```
732735
733-Create a directory for GGUF files and start `llama-server` without `--model`, `-m`, or `-hf`. Supplying one of those options starts a single model rather than the router API zot needs.
736+Create a directory for GGUF files and start `llama-server` without `--model`, `-m`, or `-hf`. Supplying one of those options starts a single model rather than the router API zut needs.
734737
735738 ```bash
736739 mkdir -p ~/llama-models
@@ -747,7 +750,7 @@ llama-server \
747750
748751 `--no-models-autoload` leaves load decisions to `/llama`. `--jinja` enables model chat templates and improves tool-call compatibility. `-ngl 999` requests maximum GPU offload, while `-c 32768` limits each loaded model to a 32K context. Adjust these values for your hardware.
749752
750-Confirm that router mode is active before configuring zot:
753+Confirm that router mode is active before configuring zut:
751754
752755 ```bash
753756 curl http://127.0.0.1:8080/health
@@ -756,7 +759,7 @@ curl http://127.0.0.1:8080/models
756759
757760 The models request must return JSON with a `data` array. A 404 usually means the server is outdated, running on a different port, or was started in single-model mode.
758761
759-In zot, run `/login`, choose **api key**, and select **llama.cpp**. Enter `http://127.0.0.1:8080` as the router URL and leave the API key empty for a local-only server. Do not enter `/v1`; zot derives the inference URL itself. The saved URL and optional key live in `$ZOT_HOME/auth.json`.
762+In zut, run `/login`, choose **api key**, and select **llama.cpp**. Enter `http://127.0.0.1:8080` as the router URL and leave the API key empty for a local-only server. Do not enter `/v1`; zut derives the inference URL itself. The saved URL and optional key live in `$ZUT_HOME/auth.json`.
760763
761764 You can configure the same connection through environment variables:
762765
@@ -775,7 +778,7 @@ Run `/llama` to:
775778 - explicitly load or unload a model
776779 - press `d` to ask the router to remove a downloaded cache model after confirmation
777780
778-Models discovered through `--models-dir` or a preset cannot be removed by zot. Delete those files from their configured source instead. The router removes the selected GGUF from its Hugging Face cache, but some llama.cpp versions retain shared repository artifacts such as `mmproj` files. Remove the repository's cache directory manually if those artifacts are no longer needed. Hugging Face search uses `HF_TOKEN` when available. Gated repositories require prior access approval, and the `llama-server` process also needs an authorized `HF_TOKEN` because the server performs the download.
781+Models discovered through `--models-dir` or a preset cannot be removed by zut. Delete those files from their configured source instead. The router removes the selected GGUF from its Hugging Face cache, but some llama.cpp versions retain shared repository artifacts such as `mmproj` files. Remove the repository's cache directory manually if those artifacts are no longer needed. Hugging Face search uses `HF_TOKEN` when available. Gated repositories require prior access approval, and the `llama-server` process also needs an authorized `HF_TOKEN` because the server performs the download.
779782
780783 Opening `/model` refreshes the router and lists every loaded model under provider `llama.cpp`. Unloaded models are intentionally omitted because they cannot answer inference requests. Load them through `/llama` first, then select them through `/model`.
781784
@@ -787,7 +790,7 @@ In the main chat, `ctrl+v` checks the system clipboard for an image before falli
787790
788791 ## Inline images
789792
790-When a tool returns an image (for example `read` on a PNG), zot renders it inline on terminals that support it: **Ghostty**, **Kitty**, **iTerm2**, **WezTerm**. On other terminals you see a text placeholder with MIME type, pixel dimensions, and byte size. Control with the `ZOT_INLINE_IMAGES` env var:
793+When a tool returns an image (for example `read` on a PNG), zut renders it inline on terminals that support it: **Ghostty**, **Kitty**, **iTerm2**, **WezTerm**. On other terminals you see a text placeholder with MIME type, pixel dimensions, and byte size. Control with the `ZUT_INLINE_IMAGES` env var:
791794
792795 | Value | Effect |
793796 |---|---|
@@ -796,15 +799,15 @@ When a tool returns an image (for example `read` on a PNG), zot renders it inlin
796799 | `kitty` | Force the Kitty graphics protocol. |
797800 | `off`, `none` | Always use the text placeholder. |
798801
799-Herdr's Kitty graphics support is currently experimental, so zot does not enable inline images there automatically. After enabling `experimental.kitty_graphics` in Herdr, set `ZOT_INLINE_IMAGES=kitty` to opt in.
802+Herdr's Kitty graphics support is currently experimental, so zut does not enable inline images there automatically. After enabling `experimental.kitty_graphics` in Herdr, set `ZUT_INLINE_IMAGES=kitty` to opt in.
800803
801-Frames containing images are full-repainted (no differential diff) to prevent stale image pixels from lingering through scroll. That costs one terminal flash per image-containing frame; set `ZOT_INLINE_IMAGES=off` if that bothers you.
804+Frames containing images are full-repainted (no differential diff) to prevent stale image pixels from lingering through scroll. That costs one terminal flash per image-containing frame; set `ZUT_INLINE_IMAGES=off` if that bothers you.
802805
803806 ## Tool rendering
804807
805-By default each tool call (bash, read, write, edit) renders inside a bordered panel — a `┌─ header ─┐`, `│`-prefixed body rows, and a `└─┘` footer. On a screen with many calls the borders can read as busy, so zot also offers a **flat** mode: a single quiet header line per call (`▌ bash …`) with indented, border-free output. Same information — tool name, arg summary, streamed output, the `... (N more lines, ctrl+o to expand)` truncation — just no frame.
808+By default each tool call (bash, read, write, edit) renders inside a bordered panel — a `┌─ header ─┐`, `│`-prefixed body rows, and a `└─┘` footer. On a screen with many calls the borders can read as busy, so zut also offers a **flat** mode: a single quiet header line per call (`▌ bash …`) with indented, border-free output. Same information — tool name, arg summary, streamed output, the `... (N more lines, ctrl+o to expand)` truncation — just no frame.
806809
807-Set the `tool_render` key in `$ZOT_HOME/config.json`:
810+Set the `tool_render` key in `$ZUT_HOME/config.json`:
808811
809812 ```json
810813 {
@@ -817,7 +820,7 @@ Set the `tool_render` key in `$ZOT_HOME/config.json`:
817820 | unset / `"box"` (default) | Each tool call is wrapped in a bordered panel. |
818821 | `"flat"` | Boxless: a quiet header line plus indented output. |
819822
820-The `ZOT_FLAT_TOOLS` env var overrides the config for a single run, which is handy for trying it without editing the file:
823+The `ZUT_FLAT_TOOLS` env var overrides the config for a single run, which is handy for trying it without editing the file:
821824
822825 | Value | Effect |
823826 |---|---|
@@ -826,18 +829,18 @@ The `ZOT_FLAT_TOOLS` env var overrides the config for a single run, which is han
826829 | unset | Fall back to the `tool_render` config key. |
827830
828831 ```sh
829-ZOT_FLAT_TOOLS=1 zot # flat, just this run
830-ZOT_FLAT_TOOLS=0 zot # boxes, even if config.json says "flat"
832+ZUT_FLAT_TOOLS=1 zut # flat, just this run
833+ZUT_FLAT_TOOLS=0 zut # boxes, even if config.json says "flat"
831834 ```
832835
833836 Either way, theme colors still drive the rendering (the header uses your accent/foreground, output uses the tool-output color) and `ctrl+o` still expands a truncated result.
834837
835838 ### Tool arg width
836839
837-The header line for a tool call shows the tool name plus a one-line summary of its primary argument — a `path`, a `command`, or a query. That summary is truncated to 60 cells by default (`web_answer What is the best architecture to implement resilience wit...`). On a wide terminal that can clip long queries more than you'd like, so set the `ZOT_TOOL_ARG_WIDTH` env var to raise (or lower) the limit:
840+The header line for a tool call shows the tool name plus a one-line summary of its primary argument — a `path`, a `command`, or a query. That summary is truncated to 60 cells by default (`web_answer What is the best architecture to implement resilience wit...`). On a wide terminal that can clip long queries more than you'd like, so set the `ZUT_TOOL_ARG_WIDTH` env var to raise (or lower) the limit:
838841
839842 ```sh
840-ZOT_TOOL_ARG_WIDTH=120 zot # allow up to 120 cells before truncating
843+ZUT_TOOL_ARG_WIDTH=120 zut # allow up to 120 cells before truncating
841844 ```
842845
843846 | Value | Effect |
@@ -861,17 +864,17 @@ By default a message you send renders as a padded, background-tinted bubble: a b
861864 | unset / `false` (default) | Padded, background-tinted user bubble. |
862865 | `true` | One quiet gutter line per wrapped row. |
863866
864-The `ZOT_COMPACT_INPUT` env var overrides the config for a single run (`1`/`true`/`on`/`compact` force compact; `0`/`false`/`off`/`bubble` force the bubble):
867+The `ZUT_COMPACT_INPUT` env var overrides the config for a single run (`1`/`true`/`on`/`compact` force compact; `0`/`false`/`off`/`bubble` force the bubble):
865868
866869 ```sh
867-ZOT_COMPACT_INPUT=1 zot # compact, just this run
870+ZUT_COMPACT_INPUT=1 zut # compact, just this run
868871 ```
869872
870873 ## Queued messages
871874
872-You can keep typing while the agent is working. Pressing `enter` during a turn queues the message instead of interrupting: it shows up above the status bar as `sliding in: <text>` and is delivered as the next user turn the moment the current one finishes. Queue as many as you want; they run in order. `esc` cancels the active turn and drops the queue so a runaway turn doesn't flood you with stale follow-ups; `ctrl+c` while busy arms the exit hint instead of interrupting, a second `ctrl+c` within two seconds exits zot.
875+You can keep typing while the agent is working. Pressing `enter` during a turn queues the message instead of interrupting: it shows up above the status bar as `sliding in: <text>` and is delivered as the next user turn the moment the current one finishes. Queue as many as you want; they run in order. `esc` cancels the active turn and drops the queue so a runaway turn doesn't flood you with stale follow-ups; `ctrl+c` while busy arms the exit hint instead of interrupting, a second `ctrl+c` within two seconds exits zut.
873876
874-To recover the most recently queued message back into the editor (to tweak it before it runs), press `Option+↑`. In VS Code's integrated terminal that chord doesn't survive xterm.js's macOS key handling — use `Option+Shift+↑` there. zot's hint line under the sliding-in queue adapts automatically based on `$TERM_PROGRAM`.
877+To recover the most recently queued message back into the editor (to tweak it before it runs), press `Option+↑`. In VS Code's integrated terminal that chord doesn't survive xterm.js's macOS key handling — use `Option+Shift+↑` there. zut's hint line under the sliding-in queue adapts automatically based on `$TERM_PROGRAM`.
875878
876879 Slash commands also work while the agent is busy. Non-destructive ones (`/help`, `/jump`, `/btw`, `/sessions`, `/skills`, `/reasoning`, `/settings`, `/jail`, `/unjail`, `/exit`) take effect immediately. Destructive ones (`/clear`, `/compact`, `/login`, `/logout`, `/model`, `/reload-ext`) cancel the active turn first and then run.
877880
@@ -927,49 +930,49 @@ Type `@` followed by a filter string to narrow the list (e.g. `@read` shows only
927930
928931 ## Extensions
929932
930-zot can be extended in any language via a subprocess + JSON-RPC protocol. Extensions can register slash commands, expose tools to the model, intercept tool calls (block or rewrite args), gate whole turns before the model is called, and rewrite the assistant's visible text before it reaches the user. None are installed by default; opt in explicitly. Hot-reload any time with `/reload-ext`.
933+zut can be extended in any language via a subprocess + JSON-RPC protocol. Extensions can register slash commands, expose tools to the model, intercept tool calls (block or rewrite args), gate whole turns before the model is called, and rewrite the assistant's visible text before it reaches the user. None are installed by default; opt in explicitly. Hot-reload any time with `/reload-ext`.
931934
932935 ### Install and manage
933936
934937 ```bash
935-zot ext install <path|git-url> # copy / clone into $ZOT_HOME/extensions/
936-zot ext install --build=go <path> # explicitly build and install local Go source
937-zot ext list # show installed extensions
938-zot ext doctor # diagnose load, registration, and conflict issues
939-zot ext logs <name> [-f] # cat or tail the extension's stderr log
940-zot ext enable <name> # re-enable a disabled extension
941-zot ext disable <name> # disable without removing
942-zot ext remove <name> # delete an extension directory
938+zut ext install <path|git-url> # copy / clone into $ZUT_HOME/extensions/
939+zut ext install --build=go <path> # explicitly build and install local Go source
940+zut ext list # show installed extensions
941+zut ext doctor # diagnose load, registration, and conflict issues
942+zut ext logs <name> [-f] # cat or tail the extension's stderr log
943+zut ext enable <name> # re-enable a disabled extension
944+zut ext disable <name> # disable without removing
945+zut ext remove <name> # delete an extension directory
943946 ```
944947
945-For local installs, zot validates `extension.json` and any executable path
948+For local installs, zut validates `extension.json` and any executable path
946949 relative to the extension directory before reporting success. It never runs a
947-build implicitly. Use `zot ext install --build=go <path>` to explicitly build a
950+build implicitly. Use `zut ext install --build=go <path>` to explicitly build a
948951 local Go extension; other source-based extensions must provide their runtime
949952 artifact or launcher themselves. A failed validation or build leaves no
950953 partial installation behind.
951954
952-`zot ext doctor` keeps normal extension startup fail-soft, but gives
955+`zut ext doctor` keeps normal extension startup fail-soft, but gives
953956 you an explicit troubleshooting view: manifest errors, disabled or
954957 shadowed extensions, subprocess load errors, ready/auto-ready status,
955958 registered commands/tools, registration conflicts, warnings, and the
956959 stderr log path.
957960
958-For development, point `zot --ext <path>` at a working directory and skip the install step entirely. Repeatable; takes precedence over installed extensions of the same name.
961+For development, point `zut --ext <path>` at a working directory and skip the install step entirely. Repeatable; takes precedence over installed extensions of the same name.
959962
960963 ### Updating extensions
961964
962-`zot update` refreshes the zot binary **and** every installed extension that lives in a git checkout. Per-extension behaviour:
965+`zut update` refreshes the zut binary **and** every installed extension that lives in a git checkout. Per-extension behaviour:
963966
964967 - Disabled extensions are skipped.
965-- Extensions without a `.git/` directory (installed by `zot ext install ./local-path`) are skipped — there is no remote to pull from.
966-- For the rest, zot stashes any dirty worktree state (including untracked runtime files like `todos.json` or `config.json`), runs `git pull --ff-only`, and pops the stash. If the pop produces conflicts, the conflict markers are left in place and you'll see a warning.
967-- Diverged branches, offline pulls, or any other git failure are reported as `failed` and the next extension is processed. `zot update` itself never aborts because of an extension.
968-- zot does **not** run any build step (`go build`, `npm install`, `make`) after the pull. Extension authors are expected to commit the runnable artifact (binary, transpiled JS, etc.). If you need a build, run it explicitly, reinstall the extension, or use `/reload-ext` for a working-tree copy.
968+- Extensions without a `.git/` directory (installed by `zut ext install ./local-path`) are skipped — there is no remote to pull from.
969+- For the rest, zut stashes any dirty worktree state (including untracked runtime files like `todos.json` or `config.json`), runs `git pull --ff-only`, and pops the stash. If the pop produces conflicts, the conflict markers are left in place and you'll see a warning.
970+- Diverged branches, offline pulls, or any other git failure are reported as `failed` and the next extension is processed. `zut update` itself never aborts because of an extension.
971+- zut does **not** run any build step (`go build`, `npm install`, `make`) after the pull. Extension authors are expected to commit the runnable artifact (binary, transpiled JS, etc.). If you need a build, run it explicitly, reinstall the extension, or use `/reload-ext` for a working-tree copy.
969972
970973 ### Theme-only extensions
971974
972-An extension may ship only a theme: `extension.json` plus `theme.json` (or `themes/theme.json`) and no executable. zot loads it without spawning a subprocess and shows it in `/settings` with source information. See [docs/themes.md](docs/themes.md).
975+An extension may ship only a theme: `extension.json` plus `theme.json` (or `themes/theme.json`) and no executable. zut loads it without spawning a subprocess and shows it in `/settings` with source information. See [docs/themes.md](docs/themes.md).
973976
974977 ### Reference
975978
@@ -977,12 +980,12 @@ An extension may ship only a theme: `extension.json` plus `theme.json` (or `them
977980
978981 ## Skills
979982
980-A skill is a per-folder `SKILL.md` file with a YAML frontmatter header. zot discovers skills at startup, surfaces their names in the system prompt, and exposes a built-in `skill` tool the model uses to load the body on demand.
983+A skill is a per-folder `SKILL.md` file with a YAML frontmatter header. zut discovers skills at startup, surfaces their names in the system prompt, and exposes a built-in `skill` tool the model uses to load the body on demand.
981984
982-By default zot loads built-in skills plus user-installed skills from:
985+By default zut loads built-in skills plus user-installed skills from:
983986
984-- `./.zot/skills/<name>/SKILL.md` (project)
985-- `$ZOT_HOME/skills/<name>/SKILL.md` (global)
987+- `./.zut/skills/<name>/SKILL.md` (project)
988+- `$ZUT_HOME/skills/<name>/SKILL.md` (global)
986989 - `./.claude/skills/<name>/SKILL.md`, `~/.claude/skills/<name>/SKILL.md` (Claude-compatible layout)
987990 - `./.agents/skills/<name>/SKILL.md`, `~/.agents/skills/<name>/SKILL.md` (agent-compatible layout)
988991
@@ -992,14 +995,14 @@ See [docs/skills.md](docs/skills.md) for the frontmatter fields, authoring tips,
992995
993996 ## Telegram bot (bridge)
994997
995-zot can run as a telegram bot so you can DM it from your phone. Two ways to run it: **from inside the TUI** (the running session mirrors into Telegram) or **as a standalone background daemon** (a headless bot with its own independent agent).
998+zut can run as a telegram bot so you can DM it from your phone. Two ways to run it: **from inside the TUI** (the running session mirrors into Telegram) or **as a standalone background daemon** (a headless bot with its own independent agent).
996999
9971000 ### From inside the TUI
9981001
9991002 Type `/telegram` in the running TUI to open a picker with **connect**, **disconnect**, and **status**. When connected:
10001003
10011004 - DMs from the paired user become prompts in the **same** session you're typing in, so you can continue a conversation from the terminal on your phone and back again.
1002-- Messages you type in the TUI are mirrored into the Telegram thread prefixed `you: ...` and the assistant's replies come back prefixed `zot: ...`, so the Telegram chat stays a complete record of both sides of the conversation.
1005+- Messages you type in the TUI are mirrored into the Telegram thread prefixed `you: ...` and the assistant's replies come back prefixed `zut: ...`, so the Telegram chat stays a complete record of both sides of the conversation.
10031006 - Messages sent from Telegram show up as your own bubble in Telegram (no mirror) and the assistant's reply to them comes back bare (no prefix).
10041007 - The status bar shows a `- tg -` tag while the bridge is active.
10051008 - `/telegram connect` / `/telegram disconnect` / `/telegram status` (or `/tg`) also work as direct commands without the picker.
@@ -1011,32 +1014,32 @@ The in-TUI bridge refuses to start while the standalone daemon (below) is runnin
10111014 For headless servers or long-running bots unattached to a TUI:
10121015
10131016 ```bash
1014-zot telegram-bot setup # paste a BotFather token, verify, save
1015-zot telegram-bot run # foreground: long-poll in this terminal (ctrl+c to stop)
1016-zot telegram-bot start # background: detach and return immediately
1017-zot telegram-bot stop # SIGTERM the background bot (SIGKILL after 5s)
1018-zot telegram-bot logs -f # tail $ZOT_HOME/logs/bot.log (omit -f to just cat)
1019-zot telegram-bot status # config (token masked) + running/stopped
1020-zot telegram-bot reset # forget the token and paired user
1021-# short alias: `zot tg ...` is accepted for every subcommand
1017+zut telegram-bot setup # paste a BotFather token, verify, save
1018+zut telegram-bot run # foreground: long-poll in this terminal (ctrl+c to stop)
1019+zut telegram-bot start # background: detach and return immediately
1020+zut telegram-bot stop # SIGTERM the background bot (SIGKILL after 5s)
1021+zut telegram-bot logs -f # tail $ZUT_HOME/logs/bot.log (omit -f to just cat)
1022+zut telegram-bot status # config (token masked) + running/stopped
1023+zut telegram-bot reset # forget the token and paired user
1024+# short alias: `zut tg ...` is accepted for every subcommand
10221025 ```
10231026
1024-The background flavor writes the child's PID to `$ZOT_HOME/bot.pid` and redirects stdout and stderr to `$ZOT_HOME/logs/bot.log`. `zot telegram-bot stop` reads that PID, sends SIGTERM, waits up to five seconds, then escalates to SIGKILL if the child is still alive. Running two instances at once is refused at startup.
1027+The background flavor writes the child's PID to `$ZUT_HOME/bot.pid` and redirects stdout and stderr to `$ZUT_HOME/logs/bot.log`. `zut telegram-bot stop` reads that PID, sends SIGTERM, waits up to five seconds, then escalates to SIGKILL if the child is still alive. Running two instances at once is refused at startup.
10251028
1026-> **Use the installed binary for `start`.** `go run ./cmd/zot telegram-bot start` won't work. `go run` builds a binary in a temp directory and deletes it when it exits, which kills the detached child. Run `make install` (or `go build`) first and invoke the installed binary.
1029+> **Use the installed binary for `start`.** `go run ./cmd/zut telegram-bot start` won't work. `go run` builds a binary in a temp directory and deletes it when it exits, which kills the detached child. Run `make install` (or `go build`) first and invoke the installed binary.
10271030
10281031 Setup flow:
10291032
10301033 1. Talk to [@BotFather](https://t.me/BotFather) on telegram, run `/newbot`, copy the token it gives you.
1031-2. Run `zot telegram-bot setup` and paste the token when prompted.
1032-3. Run `zot telegram-bot run` in the directory you want the agent to operate in.
1034+2. Run `zut telegram-bot setup` and paste the token when prompted.
1035+3. Run `zut telegram-bot run` in the directory you want the agent to operate in.
10331036 4. Open your bot on telegram, send `/start`. The first user to do this claims the bridge (stored as `allowed_user_id`); every other user is rejected.
10341037
1035-From then on, any DM you send is forwarded to the agent as a user prompt. Attached photos or `image/*` documents are downloaded and passed to vision-capable models. In-bot telegram commands are case-insensitive: `/help`, `/status`, `/stop` (cancel the current turn). Config lives in `$ZOT_HOME/bot.json` (mode 0600).
1038+From then on, any DM you send is forwarded to the agent as a user prompt. Attached photos or `image/*` documents are downloaded and passed to vision-capable models. In-bot telegram commands are case-insensitive: `/help`, `/status`, `/stop` (cancel the current turn). Config lives in `$ZUT_HOME/bot.json` (mode 0600).
10361039
10371040 Starting either Telegram bridge removes any webhook configured for that bot before long polling begins, while preserving pending updates. Telegram does not allow webhooks and `getUpdates` polling at the same time, so do not share the bot token with another service that expects to keep a webhook active.
10381041
1039-Bot mode respects the usual zot flags: `--provider`, `--model`, `--cwd`, `--reasoning`, `--continue`, `--no-session`, `--no-tools`, and so on. Run `zot tg run -c --model claude-opus-4-1` to resume the latest session on Opus, for example.
1042+Bot mode respects the usual zut flags: `--provider`, `--model`, `--cwd`, `--reasoning`, `--continue`, `--no-session`, `--no-tools`, and so on. Run `zut tg run -c --model claude-opus-4-1` to resume the latest session on Opus, for example.
10401043
10411044 ### Architecture: protocol-agnostic bot core
10421045
@@ -1048,7 +1051,7 @@ This means additional messaging backends (Discord, Slack, Signal, and similar) c
10481051
10491052 ```bash
10501053 make help # list the important developer commands
1051-make build # build ./bin/zot as 0.0.0-dev
1054+make build # build ./bin/zut as 0.0.0-dev
10521055 make test # go test -race ./...
10531056 make test-fast # go test ./...
10541057 make lint # golangci-lint + go vet + gofmt check
@@ -1062,7 +1065,7 @@ make release VERSION=0.1.0 # cross-compile release binaries
10621065 Source layout (single Go module, four packages under `packages/`):
10631066
10641067 ```
1065-cmd/zot/ main()
1068+cmd/zut/ main()
10661069 packages/provider/ LLM client surface, model catalog, streaming clients
10671070 packages/provider/auth/ credential store, api-key probe, oauth, login server
10681071 packages/core/ agent loop, sessions, cost tracking, compaction
@@ -1077,12 +1080,12 @@ packages/agent/modes/telegram/ telegram adapter, api client, daemon
10771080 packages/agent/tools/ read, write, edit, bash, lsp, sandbox
10781081 packages/agent/skills/ skill discovery, frontmatter parser, skill tool
10791082 packages/agent/subagents/ named profiles, supervisor, and background runtime
1080-packages/agent/sdk/ public Go SDK for embedding zot in-process (package sdk)
1083+packages/agent/sdk/ public Go SDK for embedding zut in-process (package sdk)
10811084 packages/agent/ext/ public Go SDK for writing extensions (package ext)
10821085 ```
10831086
10841087 Downstream consumers can depend on individual packages:
1085-`go get github.com/patriceckhart/zot/packages/core` pulls only `core` and its transitive deps (today: `provider`), no agent or TUI code.
1088+`go get github.com/bnema/zut/packages/core` pulls only `core` and its transitive deps (today: `provider`), no agent or TUI code.
10861089
10871090 ## License
10881091
R cmd/zot/main.go → cmd/zut/main.go +5 -5 renamed, 88% similar
@@ -1,4 +1,4 @@
1-// Command zot is a lightweight terminal coding agent.
1+// Command zut is a lightweight terminal coding agent.
22 package main
33
44 import (
@@ -8,12 +8,12 @@ import (
88 "runtime/debug"
99 "strings"
1010
11- "github.com/patriceckhart/zot/packages/agent"
11+ "github.com/bnema/zut/packages/agent"
1212 )
1313
1414 // Injected at build time via -ldflags "-X main.version=... -X main.commit=... -X main.date=...".
1515 // See .goreleaser.yaml for the release build and the Makefile for
16-// local builds. Defaults make `zot --version` print something sensible
16+// local builds. Defaults make `zut --version` print something sensible
1717 // when built without ldflags.
1818 var (
1919 // 0.0.0-dev is the placeholder for local / untagged builds.
@@ -37,12 +37,12 @@ func main() {
3737 v = v + ")"
3838 }
3939 if err := agent.Run(os.Args[1:], v); err != nil {
40- fmt.Fprintln(os.Stderr, "zot:", err)
40+ fmt.Fprintln(os.Stderr, "zut:", err)
4141 os.Exit(1)
4242 }
4343 }
4444
45-// resolvedVersion falls back to the module version embedded by Go when zot is
45+// resolvedVersion falls back to the module version embedded by Go when zut is
4646 // installed with "go install ...@version". Release archives still use the
4747 // version injected by GoReleaser, and local source builds remain 0.0.0-dev.
4848 func resolvedVersion(linkedVersion string) string {
R cmd/zot/main_test.go → cmd/zut/main_test.go +0 -0 renamed, 100% similar

renamed, 100% similar

M docs.go +4 -4
@@ -9,7 +9,7 @@ import (
99 "sort"
1010 )
1111
12-// embeddedDocs holds zot's user-facing documentation so installed
12+// embeddedDocs holds zut's user-facing documentation so installed
1313 // binaries do not depend on a source checkout existing on disk.
1414 //
1515 //go:embed README.md docs/*.md
@@ -23,11 +23,11 @@ var docFiles = map[string]string{
2323 "themes.md": "docs/themes.md",
2424 }
2525
26-// EnsureInstalled writes the embedded docs into $ZOT_HOME/docs and
26+// EnsureInstalled writes the embedded docs into $ZUT_HOME/docs and
2727 // returns that directory. Existing files are left untouched when their
2828 // content already matches the embedded copy.
29-func EnsureInstalled(zotHome string) (string, error) {
30- dir := filepath.Join(zotHome, "docs")
29+func EnsureInstalled(zutHome string) (string, error) {
30+ dir := filepath.Join(zutHome, "docs")
3131 if err := os.MkdirAll(dir, 0o755); err != nil {
3232 return dir, err
3333 }
M docs/extensions.md +84 -80
@@ -1,6 +1,6 @@
1-# zot extensions
1+# zut extensions
22
3-zot can be extended with custom slash commands by running an external
3+zut can be extended with custom slash commands by running an external
44 program as a subprocess and exchanging newline-delimited JSON over
55 its stdin/stdout. Extensions can be written in **any language** that
66 can read and write JSON lines from stdio — Go, TypeScript, Python,
@@ -12,7 +12,7 @@ Six phases shipped so far:
1212 - **Phase 2**: tools the LLM can call.
1313 - **Phase 3**: lifecycle event subscriptions + tool-call interception
1414 for guardrail extensions.
15-- **Phase 4**: interactive extension-owned panels rendered inside zot.
15+- **Phase 4**: interactive extension-owned panels rendered inside zut.
1616 - **Phase 5**: branch-aware session lifecycle events and opaque extension
1717 state persisted with session files.
1818 - **Phase 6**: hidden per-turn context, persistent status/widgets, and
@@ -28,18 +28,20 @@ no SDK required:
2828
2929 ```python
3030 #!/usr/bin/env python3
31-# $ZOT_HOME/extensions/hello-py/hello.py
31+# $ZUT_HOME/extensions/hello-py/hello.py
3232 import json, sys
3333
3434 def emit(obj):
3535 sys.stdout.write(json.dumps(obj) + "\n")
3636 sys.stdout.flush()
3737
38-emit({"type":"hello","name":"hello-py","version":"1.0.0","capabilities":["commands"]})
38+emit({"type":"hello","protocol_version":2,"name":"hello-py","version":"1.0.0","capabilities":["commands"]})
3939
4040 for line in sys.stdin:
4141 msg = json.loads(line)
4242 if msg["type"] == "hello_ack":
43+ if msg.get("protocol_version") != 2:
44+ raise SystemExit("unsupported extension protocol version")
4345 emit({"type":"register_command","name":"hellopy","description":"say hi (python)"})
4446 emit({"type":"ready"})
4547 elif msg["type"] == "command_invoked":
@@ -63,58 +65,58 @@ Drop it in a directory with this `extension.json`:
6365 ```
6466
6567 `exec` is required for protocol extensions. If an extension only ships
66-`theme.json` or `themes/theme.json`, no `exec` is required and zot does
68+`theme.json` or `themes/theme.json`, no `exec` is required and zut does
6769 not spawn a subprocess.
6870
6971 `chmod +x hello.py`, install:
7072
7173 ```bash
72-zot ext install ./hello-py
74+zut ext install ./hello-py
7375 ```
7476
75-Restart `zot`, type `/hellopy`, the agent greets you. Done.
77+Restart `zut`, type `/hellopy`, the agent greets you. Done.
7678
7779 ## Built-in extensions
7880
79-**zot ships with no extensions installed by default.** A fresh `zot install` (or `go install`) gives you a clean agent. Extensions are entirely opt-in: you install (or `--ext` for one run) only the ones you want.
81+**zut ships with no extensions installed by default.** A fresh `zut install` (or `go install`) gives you a clean agent. Extensions are entirely opt-in: you install (or `--ext` for one run) only the ones you want.
8082
8183 The `examples/extensions/` directory in the repo is reference code, not a default install set. To use any of those:
8284
8385 ```bash
8486 # install a Go example and explicitly build it in the staged install
85-zot ext install --build=go path/to/zot/examples/extensions/hello
87+zut ext install --build=go path/to/zut/examples/extensions/hello
8688
8789 # or build it in the source tree for a one-run development load
88-cd path/to/zot/examples/extensions/hello
90+cd path/to/zut/examples/extensions/hello
8991 go build -o hello .
90-zot --ext .
92+zut --ext .
9193 ```
9294
93-`zot ext install` never builds source code implicitly. For local installs it
95+`zut ext install` never builds source code implicitly. For local installs it
9496 validates `extension.json` and any executable path relative to the extension
9597 directory, then stages the copy so a missing or ignored runtime artifact fails
9698 with an explicit error instead of becoming a broken installed extension. For a
9799 local Go extension, opt into the fixed builder explicitly with
98-`zot ext install --build=go <path>`. The builder runs `go build -trimpath` from
100+`zut ext install --build=go <path>`. The builder runs `go build -trimpath` from
99101 the source directory, writes the manifest-declared relative executable into the
100102 staging directory, and validates it before installation. It may resolve Go
101103 modules and access the network according to the user's Go environment. Bare
102-launchers such as `go`, `node`, and `npx` remain resolved from `PATH` when zot
104+launchers such as `go`, `node`, and `npx` remain resolved from `PATH` when zut
103105 starts; they cannot be used as the output of `--build=go`.
104106
105107 Nothing is auto-installed and nothing reaches out to the network without your explicit action.
106108
107109 ## Layout & discovery
108110
109-zot scans two directories on startup, in this order:
111+zut scans two directories on startup, in this order:
110112
111-1. **Project-local**: `./.zot/extensions/<name>/extension.json`
112-2. **Global**: `$ZOT_HOME/extensions/<name>/extension.json`
113+1. **Project-local**: `./.zut/extensions/<name>/extension.json`
114+2. **Global**: `$ZUT_HOME/extensions/<name>/extension.json`
113115
114116 A project-local extension with the same name wins over a global one.
115-When `XDG_STATE_HOME` is set on any platform, `$ZOT_HOME` defaults to
116-`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`
117-on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.
117+When `XDG_STATE_HOME` is set on any platform, `$ZUT_HOME` defaults to
118+`$XDG_STATE_HOME/zut`. Otherwise it defaults to `~/Library/Application Support/zut/`
119+on macOS, `~/.local/state/zut` on Linux, or `%LOCALAPPDATA%\zut` on Windows.
118120
119121 Because each extension owns its own directory, the recommended place
120122 for extension state is inside that directory itself (for example
@@ -123,7 +125,7 @@ extension). The host also passes this path back in `hello_ack` as
123125 `extension_dir` / `data_dir` so runtime code does not need to guess it.
124126
125127 Each extension owns its own subdirectory. The `extension.json`
126-manifest tells zot how to launch it:
128+manifest tells zut how to launch it:
127129
128130 ```json
129131 {
@@ -140,31 +142,32 @@ manifest tells zot how to launch it:
140142
141143 | field | meaning |
142144 |---|---|
143-| `name` | required. how zot identifies the extension; must match what's sent in the `hello` frame. |
144-| `version` | optional. shown in `zot ext list`. |
145+| `name` | required. how zut identifies the extension; must match what's sent in the `hello` frame. |
146+| `version` | optional. shown in `zut ext list`. |
145147 | `exec` | required. path to the executable (relative to the manifest). |
146148 | `args` | optional. extra argv passed to `exec`. |
147149 | `language` | optional. informational only (`go`, `python`, `typescript`, ...). |
148-| `description` | optional. shown in `zot ext list`. |
150+| `description` | optional. shown in `zut ext list`. |
149151 | `skills` | optional. relative directories containing bundled `<name>/SKILL.md` files or a direct `SKILL.md`. Paths must remain inside the extension directory. |
150152 | `enabled` | optional, defaults to `true`. set to `false` to disable without removing. |
151153
152154 ## Lifecycle
153155
154-1. **Discovery**: zot reads every `extension.json` in the search dirs.
156+1. **Discovery**: zut reads every `extension.json` in the search dirs.
155157 2. **Spawn**: enabled extensions are launched as subprocesses. stderr
156- redirects to `$ZOT_HOME/logs/ext-<name>.log` (one file per
158+ redirects to `$ZUT_HOME/logs/ext-<name>.log` (one file per
157159 extension, append-mode).
158160 3. **Hello handshake**: the extension's first stdout frame must be
159- `hello`; zot replies with `hello_ack` containing the protocol
160- version, the active provider/model/cwd, and the extension's own
161- data directory so it can persist files beside its manifest.
161+ `hello` with `protocol_version: 2`; zut rejects a mismatched major
162+ and replies with `hello_ack` containing the same protocol version,
163+ the active provider/model/cwd, and the extension's own data directory
164+ so it can persist files beside its manifest.
162165 4. **Registration**: after receiving `hello_ack`, the extension sends
163166 `register_command`, `register_tool`, and subscription frames, then
164167 sends `ready`. First-come-first-served: a name already taken by a
165168 built-in or by a previously-loaded extension is silently shadowed
166169 (logged in the extension's own log file).
167-5. **Runtime**: after `ready`, zot dispatches `command_invoked` frames
170+5. **Runtime**: after `ready`, zut dispatches `command_invoked` frames
168171 when the user runs a registered command; the extension responds
169172 with `command_response`. Extensions can also push `notify`, persistent
170173 status/widget, and structured `alert` frames during runtime. Extensions
@@ -172,12 +175,12 @@ manifest tells zot how to launch it:
172175 and their own restored opaque state. Panel-capable extensions may open an
173176 interactive panel, receive key events, and push redraws while the panel is
174177 focused.
175-6. **Shutdown**: when zot exits, it sends `shutdown` and waits up to
178+6. **Shutdown**: when zut exits, it sends `shutdown` and waits up to
176179 2s for the extension to send `shutdown_ack`. Holdouts are
177180 SIGTERM'd, then SIGKILL'd.
178181
179-A crashing extension does not bring down zot. The slash command it
180-owned simply stops working until the extension is fixed and zot is
182+A crashing extension does not bring down zut. The slash command it
183+owned simply stops working until the extension is fixed and zut is
181184 restarted.
182185
183186 ## Wire format
@@ -193,7 +196,7 @@ or any other stdout frame before `hello`.
193196 #### `hello` (required, first frame)
194197
195198 ```json
196-{"type":"hello","name":"weather","version":"1.0.0",
199+{"type":"hello","protocol_version":2,"name":"weather","version":"1.0.0",
197200 "capabilities":["commands","tools","alerts","panels"]}
198201 ```
199202
@@ -204,7 +207,7 @@ or any other stdout frame before `hello`.
204207 "description":"current weather for a city"}
205208 ```
206209
207-Command names are matched case-insensitively. zot sends `command_invoked.name` using the canonical spelling registered here. Registrations that differ only by case conflict, and the first registration remains active.
210+Command names are matched case-insensitively. zut sends `command_invoked.name` using the canonical spelling registered here. Registrations that differ only by case conflict, and the first registration remains active.
208211
209212 #### `register_tool`
210213
@@ -233,11 +236,11 @@ Set `"deferred": true` to register a tool without advertising its definition ini
233236 "activate_tools":["weather"]}
234237 ```
235238
236-On Kimi K3's OpenAI-compatible routes, zot places newly activated schemas at the tool-result position using Kimi's native deferred-tool format. Other models receive the complete active tool list on the next request. Unknown names are ignored. The Go extension SDK exposes `DeferredTool` and `ToolResult.ActivateTools` for the same protocol.
239+On Kimi K3's OpenAI-compatible routes, zut places newly activated schemas at the tool-result position using Kimi's native deferred-tool format. Other models receive the complete active tool list on the next request. Unknown names are ignored. The Go extension SDK exposes `DeferredTool` and `ToolResult.ActivateTools` for the same protocol.
237240
238241 #### `ready`
239242
240-Sentinel telling zot "all initial registrations are flushed". Send it
243+Sentinel telling zut "all initial registrations are flushed". Send it
241244 right after your last `register_*` frame so the host can build the
242245 agent's tool registry without racing the registration window.
243246
@@ -259,7 +262,7 @@ registered deferred tools that become available after this result.
259262 "details":{"state":{"version":1}}}
260263 ```
261264
262-`details` is opaque extension metadata. Zot excludes it from provider
265+`details` is opaque extension metadata. Zut excludes it from provider
263266 requests and persists it as the latest extension state for the active
264267 session branch. Keep it valid JSON and reasonably small; the host caps
265268 persisted snapshots at 256 KiB. The next `session_opened`,
@@ -284,9 +287,9 @@ Recognised event names include `session_start`, `session_opened`,
284287
285288 Session events contain `session` with the current branch ID, parent ID,
286289 path, cwd, and fork point. They also contain `state`, but only for the
287-receiving extension. Zot never exposes another extension's state.
290+receiving extension. Zut never exposes another extension's state.
288291
289-`tool_confirmation_requested` fires only when zot is about to wait for
292+`tool_confirmation_requested` fires only when zut is about to wait for
290293 interactive approval. Calls running in yolo mode, calls covered by a
291294 remembered approval, and calls blocked before confirmation do not emit it.
292295 The event includes `tool_id`, `tool_name`, and the short `tool_preview`
@@ -353,9 +356,9 @@ subsequent interceptor sees the previous one's output.
353356 - `"display"` — appends `display` to the chat as a one-shot styled
354357 note. No model call, nothing written to the transcript.
355358 - `"open_panel"` — opens an extension-owned interactive panel inside
356- zot. The panel content lives in `open_panel`.
359+ zut. The panel content lives in `open_panel`.
357360 - `"noop"` — the extension handled it itself (e.g. it pushed
358- `notify` frames or kicked off background work). zot doesn't change
361+ `notify` frames or kicked off background work). zut doesn't change
359362 the UI in response.
360363
361364 Example:
@@ -370,13 +373,13 @@ Example:
370373 }}
371374 ```
372375
373-If `error` is non-empty, zot renders it as a red status line
376+If `error` is non-empty, zut renders it as a red status line
374377 regardless of `action`.
375378
376379 #### `submit` (one-way, any time)
377380
378381 Submits text as a user prompt in the interactive host. If the agent is
379-idle, zot starts a turn immediately. If a turn is already running, zot
382+idle, zut starts a turn immediately. If a turn is already running, zut
380383 queues the prompt behind it using the same queue path as typed input.
381384 Empty or whitespace-only text is ignored.
382385
@@ -419,7 +422,7 @@ Sets or replaces one status item owned by the extension. Sending an empty
419422 #### `widget` (one-way, persistent)
420423
421424 Sets or replaces a compact widget. `position` is a host-defined placement
422-hint with these interactive-zot values:
425+hint with these interactive-zut values:
423426
424427 - `"above_input"` keeps the widget in the existing persistent chrome above
425428 the editor.
@@ -498,18 +501,19 @@ Sent in response to `shutdown`. Extension should exit promptly after.
498501 #### `hello_ack`
499502
500503 ```json
501-{"type":"hello_ack","protocol_version":1,
502- "zot_version":"0.0.7","provider":"anthropic",
503- "model":"claude-opus-4-7","cwd":"/Users/pat/Developer/zot",
504- "extension_dir":"/Users/pat/Developer/zot/.zot/extensions/todos",
505- "data_dir":"/Users/pat/Developer/zot/.zot/extensions/todos",
504+{"type":"hello_ack","protocol_version":2,
505+ "zut_version":"0.0.7","provider":"anthropic",
506+ "model":"claude-opus-4-7","cwd":"/path/to/zut",
507+ "extension_dir":"/path/to/zut/.zut/extensions/todos",
508+ "data_dir":"/path/to/zut/.zut/extensions/todos",
506509 "session":{"id":"branch-1","parent_id":"root-1",
507- "path":".../session.jsonl","cwd":"/Users/pat/Developer/zot",
510+ "path":".../session.jsonl","cwd":"/path/to/zut",
508511 "fork_point":4}}
509512 ```
510513
511-Sent immediately after `hello`. Wait for this frame before sending
512-registrations if they depend on host metadata. The extension can use
514+Sent immediately after a matching `hello`. The protocol version is a
515+major and must match the version sent by the extension. Wait for this frame
516+before sending registrations if they depend on host metadata. The extension can use
513517 these fields to decide which commands to register (e.g. only register
514518 a Python tool on macOS, only register a model-specific shortcut for
515519 opus, etc.). `cwd` is the user's project directory; the extension
@@ -562,7 +566,7 @@ Lifecycle notification for events the extension subscribed to via
562566
563567 #### `event_intercept`
564568
565-Sent when zot wants to give the extension a chance to block, modify,
569+Sent when zut wants to give the extension a chance to block, modify,
566570 or annotate a lifecycle event before it happens. Reply with
567571 `event_intercept_response` within 5s; missing the deadline is
568572 treated as "allow".
@@ -598,7 +602,7 @@ name (`up`, `down`, `left`, `right`, `enter`, `esc`, `tab`, `pageup`,
598602
599603 #### `panel_close`
600604
601-Sent when the user closes the focused panel from zot (for example with
605+Sent when the user closes the focused panel from zut (for example with
602606 Esc or Ctrl+C). The extension should treat this as the panel lifetime
603607 ending and stop sending `panel_render` updates for that `panel_id`.
604608
@@ -608,29 +612,29 @@ ending and stop sending `panel_render` updates for that `panel_id`.
608612
609613 #### `shutdown`
610614
611-Sent during graceful zot exit (or `/reload-ext` once that lands).
615+Sent during graceful zut exit (or `/reload-ext` once that lands).
612616 Reply with `shutdown_ack` and then exit.
613617
614618 ## Managing extensions from the CLI
615619
616620 ```
617-zot ext list list installed extensions and their state
618-zot ext doctor diagnose load, registration, and conflict issues
619-zot ext install <path|git-url> copy / clone into $ZOT_HOME/extensions/
620-zot ext install --build=go <local-path> build local Go source, then install
621-zot ext remove <name> delete an extension directory
622-zot ext enable <name> re-enable a disabled extension
623-zot ext disable <name> disable without removing
624-zot ext logs <name> [-f] cat / tail the extension's stderr
621+zut ext list list installed extensions and their state
622+zut ext doctor diagnose load, registration, and conflict issues
623+zut ext install <path|git-url> copy / clone into $ZUT_HOME/extensions/
624+zut ext install --build=go <local-path> build local Go source, then install
625+zut ext remove <name> delete an extension directory
626+zut ext enable <name> re-enable a disabled extension
627+zut ext disable <name> disable without removing
628+zut ext logs <name> [-f] cat / tail the extension's stderr
625629 ```
626630
627-`zot ext doctor` runs the same discovery path as zot startup, but reports
631+`zut ext doctor` runs the same discovery path as zut startup, but reports
628632 what happened instead of changing the fail-soft runtime behavior. It shows
629633 manifest errors, disabled or shadowed extensions, subprocess load errors,
630634 ready/auto-ready status, registered commands/tools, registration conflicts,
631635 warnings, and each extension's stderr log path.
632636
633-`zot ext install <path>` does a recursive copy; `<git-url>` does a
637+`zut ext install <path>` does a recursive copy; `<git-url>` does a
634638 shallow clone. Both validate that the destination contains an
635639 `extension.json` and roll back if not. No build runs unless the local install
636640 is invoked with the explicit `--build=go` option. That option currently supports
@@ -639,17 +643,17 @@ only local Go source paths; clone remote source locally before building it.
639643 ## Loading an extension for one run
640644
641645 For iteration on a working copy, skip the install + reload cycle
642-and load straight from disk for one zot session:
646+and load straight from disk for one zut session:
643647
644648 ```
645-zot --ext ./my-extension # short form: -e ./my-extension
646-zot --ext ./a -e ./b # repeatable
649+zut --ext ./my-extension # short form: -e ./my-extension
650+zut --ext ./a -e ./b # repeatable
647651 ```
648652
649653 `--ext` paths take precedence over installed extensions of the same
650654 name, so you can shadow an installed copy with a work-in-progress
651655 version without uninstalling first. Nothing is copied or persisted;
652-the extension dies with zot like any other subprocess.
656+the extension dies with zut like any other subprocess.
653657
654658 ## SDKs
655659
@@ -663,7 +667,7 @@ package main
663667
664668 import (
665669 "encoding/json"
666- "github.com/patriceckhart/zot/packages/agent/ext"
670+ "github.com/bnema/zut/packages/agent/ext"
667671 )
668672
669673 func main() {
@@ -697,10 +701,10 @@ func main() {
697701 ```
698702
699703 Build with `go build -o hello .`, drop the binary + an `extension.json`
700-into `$ZOT_HOME/extensions/hello/`.
704+into `$ZUT_HOME/extensions/hello/`.
701705
702706 `OnHello` is optional. Use it when configuration or registrations need
703-host metadata such as `HostInfo.CWD`, `Provider`, `Model`, `ZotVersion`,
707+host metadata such as `HostInfo.CWD`, `Provider`, `Model`, `ZutVersion`,
704708 `ExtensionDir`, or `DataDir`. The SDK sends `hello`, waits for
705709 `hello_ack`, runs `OnHello`, announces registrations, then sends `ready`.
706710
@@ -770,7 +774,7 @@ The `tasked-phases` example bundles its companion skill under
770774 `extension.json`. Installing the extension therefore installs the workflow
771775 skill automatically. The standalone copy at `examples/skills/tasked-phases/`
772776 remains useful for project-local skill installation. Its state is restored per
773-session branch when zot session persistence is enabled; the extension keeps a
777+session branch when zut session persistence is enabled; the extension keeps a
774778 project-file fallback for hosts that do not persist sessions.
775779
776780 ### Hot reload
@@ -778,7 +782,7 @@ project-file fallback for hosts that do not persist sessions.
778782 Type `/reload-ext` in the TUI to tear down every running extension
779783 subprocess, re-read the manifests from disk, and respawn the set.
780784 The agent's tool registry is rebuilt automatically, so freshly-
781-registered extension tools become callable without restarting zot.
785+registered extension tools become callable without restarting zut.
782786 Useful while developing an extension: edit, save, `/reload-ext`,
783787 done. Explicit `--ext` paths are remembered and reloaded alongside
784788 discovered extensions. The temporary reload status reports each load
@@ -803,9 +807,9 @@ Extensions run with **the user's full filesystem and network
803807 permissions**. Treat installing an extension the same as installing
804808 any other binary on your machine.
805809
806-`zot ext install <git-url>` clones from any URL you give it. There's
810+`zut ext install <git-url>` clones from any URL you give it. There's
807811 no sandbox in v1; if you need isolation, install only extensions you
808-trust or run zot under your platform's sandboxing tool (`bwrap` /
812+trust or run zut under your platform's sandboxing tool (`bwrap` /
809813 `sandbox-exec` / AppContainer).
810814
811815 ## Roadmap
@@ -814,7 +818,7 @@ Phase 1 (shipped):
814818 - [x] subprocess lifecycle + hello handshake
815819 - [x] `register_command` + `command_invoked`
816820 - [x] `notify` + `clear_notes` + structured terminal alerts
817-- [x] `zot ext` CLI
821+- [x] `zut ext` CLI
818822
819823 Phase 2 (shipped):
820824 - [x] `register_tool` + `tool_call` + `tool_result`
@@ -832,7 +836,7 @@ Phase 4 (shipped):
832836 addition to `tool_call`)
833837 - [x] modify tool args mid-flight via `modified_args`
834838 - [x] rewrite user-visible assistant text via `replace_text`
835-- [x] `/reload-ext` slash command (hot-reload without restarting zot)
839+- [x] `/reload-ext` slash command (hot-reload without restarting zut)
836840
837841 Phase 5 (shipped):
838842 - [x] session/branch identity in `hello_ack` and lifecycle events
M docs/lsp.md +8 -8
@@ -1,18 +1,18 @@
11 # LSP and linter support
22
3-zot exposes a built-in `lsp` tool for code intelligence and diagnostics. It
3+zut exposes a built-in `lsp` tool for code intelligence and diagnostics. It
44 communicates with stdio language servers through JSON-RPC and can also run
55 configured command-line linters without a shell.
66
77 ## Configuration
88
9-zot loads JSON configuration from the following layers, in increasing
9+zut loads JSON configuration from the following layers, in increasing
1010 precedence:
1111
12-- `$ZOT_HOME/lsp.json`
12+- `$ZUT_HOME/lsp.json`
1313 - `<cwd>/.pi/lsp.json`
1414 - `<cwd>/.omp/lsp.json`
15-- `<cwd>/lsp.json`, `<cwd>/.lsp.json`, and `<cwd>/.zot/lsp.json`
15+- `<cwd>/lsp.json`, `<cwd>/.lsp.json`, and `<cwd>/.zut/lsp.json`
1616
1717 A file may put providers below `servers`, `lspServers`, or `linters`, use a
1818 flat provider map, or use the `providers` array:
@@ -43,7 +43,7 @@ The `providers` array accepts built-in names and objects with `id`, `kind`,
4343 Unknown provider metadata is ignored. Set `autoDetect` to `false` to
4444 keep only providers named or defined by the configuration file.
4545
46-Server overrides merge onto zot's built-in definitions. Built-ins cover common
46+Server overrides merge onto zut's built-in definitions. Built-ins cover common
4747 Go, Rust, TypeScript/JavaScript, Python, C/C++, YAML, JSON, and shell servers,
4848 plus ESLint, golangci-lint, and Ruff CLI providers. A provider is selected
4949 when it is enabled and its file type/root markers match the workspace. Command
@@ -125,14 +125,14 @@ diagnostics allows a later recurrence to be surfaced again.
125125
126126 The sub-agent setting does not grant access to a child whose profile or
127127 explicit tool list omits `lsp`. Both settings can also be controlled through
128-`$ZOT_HOME/config.json`.
128+`$ZUT_HOME/config.json`.
129129
130130 ## Safety and lifecycle
131131
132132 Language servers and linters run as local child processes. Project and
133133 global LSP configuration is executable local configuration: review configured
134134 commands, arguments, environment variables, and workspace-local binaries before
135-using it. zot does not add a separate trust prompt or sandbox around those child
135+using it. zut does not add a separate trust prompt or sandbox around those child
136136 processes; it does avoid shell expansion and keeps returned workspace edits
137137 inside the session workspace. The `lsp` tool never executes configured commands
138138 through a shell. Workspace edits returned by
@@ -141,7 +141,7 @@ resource operations, validate UTF-16 LSP positions, and write through a
141141 temporary file before replacement. Server-initiated `workspace/applyEdit`
142142 requests use the same checks.
143143
144-LSP clients are cached per workspace and provider for the lifetime of the zot
144+LSP clients are cached per workspace and provider for the lifetime of the zut
145145 process. `reload` and process shutdown terminate cached children. Server
146146 stderr is kept out of model-facing tool results; diagnostics and protocol
147147 errors are returned through the established tool result channel.
M docs/providers.md +31 -31
@@ -1,14 +1,14 @@
1-# zot providers
1+# zut providers
22
3-zot ships with built-in providers and a model catalog. You can select models
4-with `/model`, list them with `zot --list-models`, and add private models in
5-`$ZOT_HOME/models.json`.
3+zut ships with built-in providers and a model catalog. You can select models
4+with `/model`, list them with `zut --list-models`, and add private models in
5+`$ZUT_HOME/models.json`.
66
77 ## Login methods
88
99 Use `/login` in interactive mode. Type in either provider picker to filter the list by provider ID or display name.
1010
11-- `api key`: stores an API key in `$ZOT_HOME/auth.json` when the provider uses a normal key.
11+- `api key`: stores an API key in `$ZUT_HOME/auth.json` when the provider uses a normal key.
1212 - `subscription`: stores OAuth credentials for subscription-backed providers.
1313
1414 Use `/logout` to remove stored credentials.
@@ -20,7 +20,7 @@ sessions.
2020
2121 ### Command-backed API keys
2222
23-A provider can obtain its API key from a password manager or another local program. Configure this directly in `$ZOT_HOME/auth.json`:
23+A provider can obtain its API key from a password manager or another local program. Configure this directly in `$ZUT_HOME/auth.json`:
2424
2525 ```json
2626 {
@@ -49,9 +49,9 @@ Custom provider credentials use the same object under `additional_api_key_creds`
4949 }
5050 ```
5151
52-The program is started directly rather than through a shell. The timeout defaults to 120 seconds. It must write one non-empty line to stdout, with no more than 64 KiB of output. Trailing CR/LF characters are removed. Successful results stay only in process memory and are reused until zot exits.
52+The program is started directly rather than through a shell. The timeout defaults to 120 seconds. It must write one non-empty line to stdout, with no more than 64 KiB of output. Trailing CR/LF characters are removed. Successful results stay only in process memory and are reused until zut exits.
5353
54-Command-backed credentials count as logged in without being executed. zot materializes one only when selecting that provider; background model discovery skips it. `/login` with a normal key replaces the command, while `/logout` clears it. Because `auth.json` can cause program execution, keep it user-writable only and do not use files from untrusted sources.
54+Command-backed credentials count as logged in without being executed. zut materializes one only when selecting that provider; background model discovery skips it. `/login` with a normal key replaces the command, while `/logout` clears it. Because `auth.json` can cause program execution, keep it user-writable only and do not use files from untrusted sources.
5555
5656 Setup-instruction providers:
5757
@@ -73,7 +73,7 @@ These providers support subscription login:
7373 | xAI | SuperGrok or X Premium device-code login. The browser URL is prefilled with the device code. |
7474 | GitHub Copilot | GitHub Copilot token flow. |
7575
76-OAuth tokens are stored in `$ZOT_HOME/auth.json` and refreshed when refresh is
76+OAuth tokens are stored in `$ZUT_HOME/auth.json` and refreshed when refresh is
7777 available.
7878
7979 ## API-key providers
@@ -119,7 +119,7 @@ show instructions and should be configured with environment variables.
119119
120120 OpenAI, OpenAI Responses, and OpenAI Codex requests can opt into OpenAI's
121121 Fast service tier from `/settings` by enabling **fast mode**. The setting is
122-off by default and is stored as `fast_mode` in `$ZOT_HOME/config.json`.
122+off by default and is stored as `fast_mode` in `$ZUT_HOME/config.json`.
123123
124124 Fast mode is currently limited to those OpenAI provider IDs. Enabling it while
125125 using another provider returns an error instead of silently changing that
@@ -129,7 +129,7 @@ Example:
129129
130130 ```bash
131131 export OPENROUTER_API_KEY=...
132-zot --provider openrouter
132+zut --provider openrouter
133133 ```
134134
135135 ## Local llama.cpp router
@@ -143,7 +143,7 @@ brew install llama.cpp
143143 # use `brew upgrade llama.cpp` for an existing installation
144144 ```
145145
146-Start `llama-server` without `--model`, `-m`, or `-hf`. Those flags select one model and disable the router behavior zot expects.
146+Start `llama-server` without `--model`, `-m`, or `-hf`. Those flags select one model and disable the router behavior zut expects.
147147
148148 ```bash
149149 mkdir -p ~/llama-models
@@ -160,7 +160,7 @@ llama-server \
160160
161161 This configuration discovers GGUF files below `~/llama-models`, leaves model loading under explicit user control, enables chat templates, requests GPU offload, and sets a 32K context. Tune the GPU layers and context size for the available memory.
162162
163-Check the management API before opening zot:
163+Check the management API before opening zut:
164164
165165 ```bash
166166 curl http://127.0.0.1:8080/health
@@ -169,7 +169,7 @@ curl http://127.0.0.1:8080/models
169169
170170 `/models` must return JSON with a `data` array. A 404 indicates the wrong URL, an older server build, or single-model mode.
171171
172-To save the connection, run `/login`, choose `api key`, and select `llama.cpp`. Enter `http://127.0.0.1:8080`, without `/v1`, followed by an optional bearer key. zot validates the URL and stores both values in `$ZOT_HOME/auth.json`. Environment variables override the saved connection:
172+To save the connection, run `/login`, choose `api key`, and select `llama.cpp`. Enter `http://127.0.0.1:8080`, without `/v1`, followed by an optional bearer key. zut validates the URL and stores both values in `$ZUT_HOME/auth.json`. Environment variables override the saved connection:
173173
174174 ```bash
175175 export LLAMA_BASE_URL=http://127.0.0.1:8080
@@ -188,7 +188,7 @@ Ollama downloads are stored in Ollama's internal layout and do not automatically
188188
189189 ### Amazon Bedrock
190190
191-Bedrock is configured with AWS credentials, not a generic zot API-key entry.
191+Bedrock is configured with AWS credentials, not a generic zut API-key entry.
192192 Use one of these credential sources:
193193
194194 ```bash
@@ -214,13 +214,13 @@ Example:
214214
215215 ```bash
216216 AWS_BEARER_TOKEN_BEDROCK=bedrock-api-key-... AWS_REGION=us-east-1 \
217- zot --provider amazon-bedrock --model anthropic.claude-sonnet-4-5-20250929-v1:0
217+ zut --provider amazon-bedrock --model anthropic.claude-sonnet-4-5-20250929-v1:0
218218 ```
219219
220220 Some Bedrock models require regional inference-profile IDs for on-demand
221-throughput, such as `us.` or `eu.` prefixed model IDs. zot rewrites known
221+throughput, such as `us.` or `eu.` prefixed model IDs. zut rewrites known
222222 families automatically where possible. Claude Opus 5 uses
223-`global.anthropic.claude-opus-5`; zot maps the bare foundation-model ID to that
223+`global.anthropic.claude-opus-5`; zut maps the bare foundation-model ID to that
224224 global profile and supports its adaptive reasoning levels. Explicit profile IDs
225225 and ARNs are left unchanged.
226226
@@ -230,7 +230,7 @@ Vertex can use a Google API key when available:
230230
231231 ```bash
232232 export GOOGLE_CLOUD_API_KEY=...
233-zot --provider google-vertex
233+zut --provider google-vertex
234234 ```
235235
236236 For service-account or application-default credentials, set the standard
@@ -244,7 +244,7 @@ Cloudflare AI Gateway needs a Cloudflare token plus account and gateway IDs:
244244 export CLOUDFLARE_API_KEY=...
245245 export CLOUDFLARE_ACCOUNT_ID=...
246246 export CLOUDFLARE_GATEWAY_ID=...
247-zot --provider cloudflare-ai-gateway
247+zut --provider cloudflare-ai-gateway
248248 ```
249249
250250 ### Cloudflare Workers AI
@@ -254,7 +254,7 @@ Workers AI needs a Cloudflare token and account ID:
254254 ```bash
255255 export CLOUDFLARE_API_KEY=...
256256 export CLOUDFLARE_ACCOUNT_ID=...
257-zot --provider cloudflare-workers-ai
257+zut --provider cloudflare-workers-ai
258258 ```
259259
260260 ### Azure OpenAI Responses
@@ -263,10 +263,10 @@ zot --provider cloudflare-workers-ai
263263 export AZURE_OPENAI_API_KEY=...
264264 export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com
265265 export AZURE_OPENAI_API_VERSION=v1 # optional, v1 is the default
266-zot --provider azure-openai-responses
266+zut --provider azure-openai-responses
267267 ```
268268
269-The provider uses Azure's Responses API. If deployment names differ from zot
269+The provider uses Azure's Responses API. If deployment names differ from zut
270270 model IDs, map them without changing the catalog:
271271
272272 ```bash
@@ -275,8 +275,8 @@ export AZURE_OPENAI_DEPLOYMENT_NAME_MAP='gpt-5.6-luna=luna-preview,gpt-5.6-sol=s
275275
276276 ## Auth file
277277
278-Credentials are stored in `$ZOT_HOME/auth.json` with user-only permissions
279-when zot creates the file.
278+Credentials are stored in `$ZUT_HOME/auth.json` with user-only permissions
279+when zut creates the file.
280280
281281 Example:
282282
@@ -294,11 +294,11 @@ Example:
294294
295295 The top-level keys are used for providers with dedicated credential fields.
296296 Other API-key providers are stored under `additional_api_key_creds`. Prefer
297-`/login` so zot writes the correct schema.
297+`/login` so zut writes the correct schema.
298298
299299 ## Custom providers and models
300300
301-Use `$ZOT_HOME/models.json` for private models, deployment aliases, local
301+Use `$ZUT_HOME/models.json` for private models, deployment aliases, local
302302 servers, or OpenAI-compatible gateways that are not in the built-in catalog.
303303 User entries override built-in entries with the same provider and model ID, and
304304 adding a `models.json` no longer hides the built-in catalog: your entries are
@@ -333,7 +333,7 @@ not expose a model-list endpoint, custom provider keys are accepted and stored
333333 without a verification probe; an invalid key surfaces on the first model call.
334334
335335 To retrieve this custom provider's key from a password manager, add a matching
336-entry to `$ZOT_HOME/auth.json`:
336+entry to `$ZUT_HOME/auth.json`:
337337
338338 ```json
339339 {
@@ -353,17 +353,17 @@ The provider IDs in `models.json` and `auth.json` must match. Then select the
353353 custom model directly:
354354
355355 ```sh
356-zot --provider my-company --model company-llm-v2
356+zut --provider my-company --model company-llm-v2
357357 ```
358358
359359 ## Credential resolution
360360
361-For each request, zot checks credentials in this order:
361+For each request, zut checks credentials in this order:
362362
363363 1. Explicit CLI key, such as `--api-key`.
364364 2. Provider-specific environment variables (including derived custom-provider
365365 variables such as `MY_COMPANY_API_KEY`).
366-3. `$ZOT_HOME/auth.json`, including custom provider keys saved by `/login`.
366+3. `$ZUT_HOME/auth.json`, including custom provider keys saved by `/login`.
367367
368368 `models.json` itself never stores credentials; it only describes models and
369369 their endpoints.
M docs/rpc.md +12 -12
@@ -1,41 +1,41 @@
1-# zot RPC
1+# zut RPC
22
3-`zot rpc` runs the agent runtime as a subprocess that speaks newline-delimited JSON on stdin and stdout. Use it from any language that can spawn a process and read/write its pipes — Go, TypeScript, Python, Rust, shell, anything.
3+`zut rpc` runs the agent runtime as a subprocess that speaks newline-delimited JSON on stdin and stdout. Use it from any language that can spawn a process and read/write its pipes — Go, TypeScript, Python, Rust, shell, anything.
44
55 For a Go program embedding the runtime in-process, use the `packages/agent/sdk` SDK instead. The wire format below mirrors the SDK's types one-for-one so consumers can share parsing code.
66
77 ## Quick start
88
99 ```bash
10-# spawn zot rpc; talk to it from a shell
10+# spawn zut rpc; talk to it from a shell
1111 ( echo '{"id":"1","type":"prompt","message":"hello"}'; sleep 5 ) \
12- | zot rpc --provider anthropic
12+ | zut rpc --provider anthropic
1313 ```
1414
1515 You'll see one JSON object per line on stdout: a response acknowledging the prompt, a stream of events (`text_delta`, `tool_call`, `tool_result`, `usage`), then `done`.
1616
1717 ## Process model
1818
19-- One `zot rpc` process serves **one cwd, one model, one session**.
19+- One `zut rpc` process serves **one cwd, one model, one session**.
2020 - For multiple projects, spawn multiple processes.
2121 - Concurrency: at most one prompt or compact in flight at a time. A second one queues until the first finishes; aborting fires immediately.
2222 - The process exits when stdin closes.
2323
2424 ## Flags
2525
26-`zot rpc` accepts the same flags as the other modes: `--provider`, `--model`, `--cwd`, `--api-key`, `--base-url`, `--system-prompt`, `--append-system-prompt`, `--reasoning`, `--max-steps`, `--no-tools`, `--tools`. Sessions are disabled by default in RPC mode — the embedding application owns persistence.
26+`zut rpc` accepts the same flags as the other modes: `--provider`, `--model`, `--cwd`, `--api-key`, `--base-url`, `--system-prompt`, `--append-system-prompt`, `--reasoning`, `--max-steps`, `--no-tools`, `--tools`. Sessions are disabled by default in RPC mode — the embedding application owns persistence.
2727
28-RPC agents use the same resolved system-prompt policy as other modes. Ponytail coding mode is enabled when `ponytail_enabled` is missing or `true` in `$ZOT_HOME/config.json`, and disabled when it is `false`. A custom `--system-prompt` replaces the built-in identity, while enabled append addenda remain present.
28+RPC agents use the same resolved system-prompt policy as other modes. Ponytail coding mode is enabled when `ponytail_enabled` is missing or `true` in `$ZUT_HOME/config.json`, and disabled when it is `false`. A custom `--system-prompt` replaces the built-in identity, while enabled append addenda remain present.
2929
3030 ## Auth
3131
32-If the environment variable `ZOTCORE_RPC_TOKEN` is set on the spawned process, the first line on stdin **must** be a `hello` command containing the matching token:
32+If the environment variable `ZUTCORE_RPC_TOKEN` is set on the spawned process, the first line on stdin **must** be a `hello` command containing the matching token:
3333
3434 ```json
3535 {"id":"0","type":"hello","token":"shared-secret"}
3636 ```
3737
38-If absent or wrong, the response carries `success:false` and the process exits. Without `ZOTCORE_RPC_TOKEN` set, no auth is required (the spawning process is implicitly trusted; if it can spawn `zot` it can also read your `auth.json` directly).
38+If absent or wrong, the response carries `success:false` and the process exits. Without `ZUTCORE_RPC_TOKEN` set, no auth is required (the spawning process is implicitly trusted; if it can spawn `zut` it can also read your `auth.json` directly).
3939
4040 ## Wire format
4141
@@ -66,7 +66,7 @@ Response:
6666 "data":{"protocol_version":1,"version":"0.0.4","provider":"anthropic","model":"claude-opus-4-5"}}
6767 ```
6868
69-Required as the first message when `ZOTCORE_RPC_TOKEN` is set; optional otherwise.
69+Required as the first message when `ZUTCORE_RPC_TOKEN` is set; optional otherwise.
7070
7171 ### `prompt`
7272
@@ -120,7 +120,7 @@ Response data:
120120 {
121121 "provider": "anthropic",
122122 "model": "claude-opus-4-5",
123- "cwd": "/Users/pat/Developer/zot",
123+ "cwd": "/path/to/zut",
124124 "message_count": 12,
125125 "busy": false,
126126 "usage": {"input": 1234, "output": 567, "reasoning": 123, "cache_read": 890, "cache_write": 0, "cost_usd": 0.0123}
@@ -153,7 +153,7 @@ Switch model within the same provider.
153153 {"id":"7","type":"set_model","model":"claude-sonnet-4-5"}
154154 ```
155155
156-Cross-provider swaps require relaunching `zot rpc` with the new `--provider`.
156+Cross-provider swaps require relaunching `zut rpc` with the new `--provider`.
157157
158158 ### `set_reasoning`
159159
M docs/skills.md +16 -16
@@ -1,8 +1,8 @@
1-# zot skills
1+# zut skills
22
33 A skill is a reusable instruction set written as a single
44 `SKILL.md` file with a YAML frontmatter header. Unless a skill disables
5-model invocation, zot discovers it at startup and surfaces it to the model
5+model invocation, zut discovers it at startup and surfaces it to the model
66 in two ways:
77
88 1. The system prompt gains a short manifest:
@@ -49,22 +49,22 @@ There's no template engine; the model sees what you write.
4949
5050 ## Discovery
5151
52-zot looks in these directories, in priority order, and registers the
52+zut looks in these directories, in priority order, and registers the
5353 first `SKILL.md` it finds for each unique name:
5454
5555 | location | scope |
5656 |---|---|
57-| `./.zot/skills/<name>/SKILL.md` | project (native) |
58-| `$ZOT_HOME/skills/<name>/SKILL.md` | global (native) |
57+| `./.zut/skills/<name>/SKILL.md` | project (native) |
58+| `$ZUT_HOME/skills/<name>/SKILL.md` | global (native) |
5959 | `./.claude/skills/<name>/SKILL.md` | project (claude-compat) |
6060 | `~/.claude/skills/<name>/SKILL.md` | global (claude-compat) |
6161 | `./.agents/skills/<name>/SKILL.md` | project (agent-compat) |
6262 | `~/.agents/skills/<name>/SKILL.md` | global (agent-compat) |
6363
6464 The compat paths are deliberate: a `SKILL.md` written for an existing
65-skill ecosystem works in zot unchanged. Drop your existing
65+skill ecosystem works in zut unchanged. Drop your existing
6666 `.claude/skills/` or `.agents/skills/` directories into a project and
67-zot will pick them up. User skill roots are scanned recursively, so
67+zut will pick them up. User skill roots are scanned recursively, so
6868 nested files such as `~/.agents/skills/systems-backend/subskills/golang-patterns/SKILL.md`
6969 are discovered too. The frontmatter `name` is the canonical identifier;
7070 for nested user skills, the slash-separated directory path relative to the
@@ -74,20 +74,20 @@ Enabled extensions may bundle skills by declaring relative directories in
7474 `extension.json`, for example `"skills": ["skills"]`, with files at
7575 `skills/<name>/SKILL.md`. A declared directory may also contain a single
7676 `SKILL.md` directly; its directory name supplies the fallback skill name and
77-child directories are not scanned. Bundled skills are copied by `zot ext
77+child directories are not scanned. Bundled skills are copied by `zut ext
7878 install` and loaded automatically. User/project skills take precedence over
7979 bundled skills; bundled skills take precedence over embedded built-ins. When
8080 two extensions provide the same skill name, the first declaration wins. The
8181 host rejects manifest paths that escape the extension directory, including
8282 symlinked skill files that resolve outside it.
8383
84-When `XDG_STATE_HOME` is set on any platform, `$ZOT_HOME` defaults to
85-`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`
86-on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.
84+When `XDG_STATE_HOME` is set on any platform, `$ZUT_HOME` defaults to
85+`$XDG_STATE_HOME/zut`. Otherwise it defaults to `~/Library/Application Support/zut/`
86+on macOS, `~/.local/state/zut` on Linux, or `%LOCALAPPDATA%\zut` on Windows.
8787
8888 ## Inspecting installed skills
8989
90-In zot, run `/skills`. A picker lists every discovered skill with its
90+In zut, run `/skills`. A picker lists every discovered skill with its
9191 description and source path. Press enter on a row to view the full
9292 body inline. Press esc to go back.
9393
@@ -104,7 +104,7 @@ To force a specific skill, invoke it as a slash command. Typing `/skill:` opens
104104 /skill:code-review focus on security issues
105105 ```
106106
107-zot expands the command into a user message containing the complete skill
107+zut expands the command into a user message containing the complete skill
108108 body, its directory for resolving relative references, and any text following
109109 the command as the request. This bypasses model-side skill selection.
110110
@@ -136,11 +136,11 @@ See `examples/skills/` for starter skills:
136136
137137 ## Comparison to other discovery layouts
138138
139-| ecosystem | path | zot reads it? |
139+| ecosystem | path | zut reads it? |
140140 |---|---|---|
141-| (native) | `.zot/skills/<name>/SKILL.md` | yes |
141+| (native) | `.zut/skills/<name>/SKILL.md` | yes |
142142 | (claude-style) | `.claude/skills/<name>/SKILL.md` | yes |
143143 | (agent-style) | `.agents/skills/<name>/SKILL.md` | yes |
144144
145145 Cross-pollination is intentional: pick whichever convention you're
146-already using and zot tags along.
146+already using and zut tags along.
M docs/subagents.md +8 -8
@@ -1,6 +1,6 @@
11 # Named subagent profiles
22
3-zot can discover reusable named subagent definitions from markdown files with a frontmatter block. This is a common agent-profile layout and is not tied to a particular host application.
3+zut can discover reusable named subagent definitions from markdown files with a frontmatter block. This is a common agent-profile layout and is not tied to a particular host application.
44
55 ## Discovery
66
@@ -10,12 +10,12 @@ The default directory is:
1010 ~/.agents/agents/*.md
1111 ```
1212
13-zot also reads these optional locations:
13+zut also reads these optional locations:
1414
15-- directories listed in `ZOT_AGENT_PROFILES` (use the platform path-list separator)
15+- directories listed in `ZUT_AGENT_PROFILES` (use the platform path-list separator)
1616 - `~/.pi/agent/agents/*.md` as a compatibility fallback
1717
18-Profiles are read-only inputs. zot does not execute files from these directories. Project-local profile directories are not scanned automatically; use `ZOT_AGENT_PROFILES` when a project intentionally opts into an additional profile directory.
18+Profiles are read-only inputs. zut does not execute files from these directories. Project-local profile directories are not scanned automatically; use `ZUT_AGENT_PROFILES` when a project intentionally opts into an additional profile directory.
1919
2020 ## File format
2121
@@ -41,7 +41,7 @@ Supported metadata:
4141 |---|---|
4242 | `name` | Name passed to `subagent_spawn`'s `agent` field. Falls back to the filename. |
4343 | `description` | Short description shown to the main agent. |
44-| `tools` | Comma-separated or list-form tool names. zot enforces its built-in `read`, `write`, `edit`, `bash`, and `lsp` registry; the conditional `skill` tool is available when skills are enabled. Unknown names do not grant capabilities. |
44+| `tools` | Comma-separated or list-form tool names. zut enforces its built-in `read`, `write`, `edit`, `bash`, and `lsp` registry; the conditional `skill` tool is available when skills are enabled. Unknown names do not grant capabilities. |
4545 | `model` | Optional model ID. A qualified value such as `openai-codex/gpt-5.6-luna` selects both provider and model. |
4646 | `provider` | Optional separate provider ID for a model without a provider prefix. |
4747 | `thinking` / `reasoning` | Optional reasoning level: `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, or `max`. |
@@ -50,7 +50,7 @@ Supported metadata:
5050 | `inheritSkills` | Set to `false` to omit skill discovery and the conditional `skill` loader from the child; `--no-skill` has the same effect for a run. |
5151 | `fastMode` | Optional fast-mode restriction. Omit it to inherit the host setting; `false` disables fast mode for this profile; `true` only permits fast mode when the host setting is enabled. |
5252
53-Other frontmatter from another agent host is ignored when zot does not have an equivalent setting. Recursive child spawning is disabled in v1; a worker cannot invoke `subagent_spawn` to create descendants.
53+Other frontmatter from another agent host is ignored when zut does not have an equivalent setting. Recursive child spawning is disabled in v1; a worker cannot invoke `subagent_spawn` to create descendants.
5454
5555 ## Selecting a profile
5656
@@ -84,7 +84,7 @@ The interactive command also supports the same selection explicitly:
8484 /subagents new --agent implementer --reasoning high Implement the parser change
8585 ```
8686
87-Shared mode preserves the historical host working directory. For parallel coding, pass `isolation:"worktree"` to `subagent_spawn`; zot creates a detached Git worktree, captures changed files and a patch, and never merges automatically. Named profiles change the child's instructions and configuration; they are not a security sandbox. A profile's `systemPromptMode` controls its own body relative to the built-in identity; global append addenda, including enabled Ponytail coding guidance, remain present. Child credentials are transferred over stdin rather than argv or persisted metadata, and the active provider endpoint/TLS setting is inherited only when the child uses that provider. Fast mode is inherited by default. A profile with `fastMode: false` opts out, while `fastMode: true` cannot enable fast mode when the host setting is off. Non-OpenAI child providers return an unsupported-provider error instead of silently ignoring an enabled setting.
87+Shared mode preserves the historical host working directory. For parallel coding, pass `isolation:"worktree"` to `subagent_spawn`; zut creates a detached Git worktree, captures changed files and a patch, and never merges automatically. Named profiles change the child's instructions and configuration; they are not a security sandbox. A profile's `systemPromptMode` controls its own body relative to the built-in identity; global append addenda, including enabled Ponytail coding guidance, remain present. Child credentials are transferred over stdin rather than argv or persisted metadata, and the active provider endpoint/TLS setting is inherited only when the child uses that provider. Fast mode is inherited by default. A profile with `fastMode: false` opts out, while `fastMode: true` cannot enable fast mode when the host setting is off. Non-OpenAI child providers return an unsupported-provider error instead of silently ignoring an enabled setting.
8888
8989 ## Lifecycle, results, and recovery
9090
@@ -107,4 +107,4 @@ When auto-subagents is enabled, the model also receives the read-only `subagent_
107107
108108 ## Resource policy
109109
110-The persisted `subagents` config object supports `max_concurrent`, `max_concurrent_per_parent`, `max_total_spawned`, `queue_timeout`, `default_timeout`, `max_turns`, output caps, allowed tools/roots, heartbeat and idle timeouts. A missing or non-positive configured `max_turns` uses the default ceiling of 3. The `max_turns` field in `subagent_spawn` is optional: omit it to use the policy ceiling, or provide a value from 1 through the configured maximum. Limits apply to slash commands, `subagent_spawn`, and batch operations. A child cannot create descendants in v1. Per-agent timeouts are retained across reload/resume. Packaged `zot run` agents keep their declared capability ceiling by disabling subagent delegation; profiles are not a substitute for that permission boundary.
110+The persisted `subagents` config object supports `max_concurrent`, `max_concurrent_per_parent`, `max_total_spawned`, `queue_timeout`, `default_timeout`, `max_turns`, output caps, allowed tools/roots, heartbeat and idle timeouts. A missing or non-positive configured `max_turns` uses the default ceiling of 3. The `max_turns` field in `subagent_spawn` is optional: omit it to use the policy ceiling, or provide a value from 1 through the configured maximum. Limits apply to slash commands, `subagent_spawn`, and batch operations. A child cannot create descendants in v1. Per-agent timeouts are retained across reload/resume. Packaged `zut run` agents keep their declared capability ceiling by disabling subagent delegation; profiles are not a substitute for that permission boundary.
M docs/themes.md +28 -28
@@ -1,9 +1,9 @@
1-# zot themes
1+# zut themes
22
3-zot themes are JSON files that override any subset of the built-in
3+zut themes are JSON files that override any subset of the built-in
44 light/dark TUI theme. Nothing is required: a theme can change one
55 color, only the spinner, only syntax highlighting, or all of them.
6-Missing values inherit from zot's built-in default for the detected
6+Missing values inherit from zut's built-in default for the detected
77 terminal background.
88
99 ## Where themes live
@@ -11,32 +11,32 @@ terminal background.
1111 User themes are discovered from:
1212
1313 ```text
14-$ZOT_HOME/themes/*.json
14+$ZUT_HOME/themes/*.json
1515 ```
1616
1717 Open `/settings` and choose **color theme** to switch. Changes are
18-saved in `$ZOT_HOME/config.json` and apply immediately. If the selected
19-file is deleted later, zot resets the setting to the built-in auto
18+saved in `$ZUT_HOME/config.json` and apply immediately. If the selected
19+file is deleted later, zut resets the setting to the built-in auto
2020 (default) theme.
2121
2222 The built-in choices are:
2323
24-- **auto** — detect the terminal background and use zot's built-in dark or
24+- **auto** — detect the terminal background and use zut's built-in dark or
2525 light palette. This remains the default.
2626 - **inherited (from terminal)** — use the terminal's reported foreground,
2727 background, and ANSI palette. When `COLORTERM=truecolor`/`24bit` or
28- `TERM` ends in `-direct`, zot emits truecolor; otherwise it quantizes
28+ `TERM` ends in `-direct`, zut emits truecolor; otherwise it quantizes
2929 derived colors to the xterm-256 palette as a best effort. Terminals that
30- do not answer OSC color queries fall back to zot's built-in palette while
30+ do not answer OSC color queries fall back to zut's built-in palette while
3131 still using the advertised color depth.
32-- **dark** / **light** — use zot's fixed built-in palettes.
32+- **dark** / **light** — use zut's fixed built-in palettes.
3333
3434 The inherited terminal snapshot is captured at startup and reused when the
35-setting is changed live; restart zot to pick up terminal palette changes.
35+setting is changed live; restart zut to pick up terminal palette changes.
3636
3737 All theme color roles use the same terminal-color model. JSON themes may use
3838 xterm-256 indexes, ANSI SGR colors, or RGB values for any role. On terminals
39-that advertise truecolor, zot emits RGB values directly and expands indexed
39+that advertise truecolor, zut emits RGB values directly and expands indexed
4040 values through the xterm palette; otherwise RGB values fall back to the
4141 nearest xterm-256 color while indexed values remain indexed. ANSI values keep
4242 their SGR semantics, with standard foreground/background ranges normalized to
@@ -52,13 +52,13 @@ Theme files bundled with extensions are discovered in-place from loaded
5252 extension directories:
5353
5454 ```text
55-$ZOT_HOME/extensions/<extension>/theme.json
56-$ZOT_HOME/extensions/<extension>/themes/theme.json
57-<project>/.zot/extensions/<extension>/theme.json
58-<project>/.zot/extensions/<extension>/themes/theme.json
55+$ZUT_HOME/extensions/<extension>/theme.json
56+$ZUT_HOME/extensions/<extension>/themes/theme.json
57+<project>/.zut/extensions/<extension>/theme.json
58+<project>/.zut/extensions/<extension>/themes/theme.json
5959 ```
6060
61-zot does **not** copy extension themes into `$ZOT_HOME/themes`; extension
61+zut does **not** copy extension themes into `$ZUT_HOME/themes`; extension
6262 owned themes stay in the extension directory. The settings picker shows
6363 source info such as `from extension my-theme-extension`.
6464
@@ -71,7 +71,7 @@ Metadata only:
7171 ```json
7272 {
7373 "name": "my-theme",
74- "description": "Metadata only; all visuals inherit zot defaults."
74+ "description": "Metadata only; all visuals inherit zut defaults."
7575 }
7676 ```
7777
@@ -111,13 +111,13 @@ Spinner-only:
111111 ```
112112
113113 Dark-only themes still work on light terminals. If `colors.light` is
114-missing, zot applies `colors.dark` overrides on top of the built-in
114+missing, zut applies `colors.dark` overrides on top of the built-in
115115 light default. The inverse also works.
116116
117117 ```json
118118 {
119119 "name": "custom-spinner",
120- "description": "An alternative spinner for zot that only displays a single spinner text.",
120+ "description": "An alternative spinner for zut that only displays a single spinner text.",
121121 "colors": {
122122 "dark": {
123123 "spinner_frames": ["◢", "◣", "◤", "◥"],
@@ -137,7 +137,7 @@ All fields are optional.
137137 "name": "my-theme",
138138 "description": "Shown in /settings → color theme.",
139139 "color_descriptions": {
140- "accent": "Optional documentation for humans. zot ignores this object."
140+ "accent": "Optional documentation for humans. zut ignores this object."
141141 },
142142 "colors": {
143143 "dark": {
@@ -225,11 +225,11 @@ selected surfaces from the terminal foreground/background.
225225 - `fg` — default foreground text.
226226 - `muted` — secondary text, dividers, gutters, inactive hints.
227227 - `accent` — prompt bar, bullets, links, headings, active markers.
228-- `background` — optional full-row TUI background. If missing, zot uses the terminal's existing background. Experimental: terminal background colors can vary by emulator and scrollback behavior; for the most reliable result, change your terminal background color in your terminal settings instead.
228+- `background` — optional full-row TUI background. If missing, zut uses the terminal's existing background. Experimental: terminal background colors can vary by emulator and scrollback behavior; for the most reliable result, change your terminal background color in your terminal settings instead.
229229 - `user` — user role label color; mostly compatibility.
230230 - `user_bubble_bg` — background behind user message rows.
231231 - `user_bubble_fg` — foreground inside user message rows.
232-- `assistant` — assistant/zot accent and spinner text.
232+- `assistant` — assistant/zut accent and spinner text.
233233 - `tool` — tool names, success marks, diff additions.
234234 - `tool_out` — plain tool-output text.
235235 - `error` — errors, refused calls, diff deletions.
@@ -249,7 +249,7 @@ Spinner settings can appear at top level, under `colors`, or under
249249
250250 - `spinner_frames` — list of frame strings. Single-cell glyphs keep
251251 status-bar alignment clean.
252-- `spinner_messages` — list of messages; zot picks one per turn.
252+- `spinner_messages` — list of messages; zut picks one per turn.
253253 - `spinner_interval_ms` — frame interval in milliseconds. Missing or
254254 invalid falls back to 80ms.
255255
@@ -292,7 +292,7 @@ subprocess, or executable is required when the extension contains a
292292 valid theme file.
293293
294294 ```text
295-$ZOT_HOME/extensions/my-theme-extension/
295+$ZUT_HOME/extensions/my-theme-extension/
296296 ├── extension.json
297297 └── theme.json
298298 ```
@@ -303,17 +303,17 @@ $ZOT_HOME/extensions/my-theme-extension/
303303 {
304304 "name": "my-theme-extension",
305305 "version": "1.0.0",
306- "description": "Ships a zot color theme",
306+ "description": "Ships a zut color theme",
307307 "enabled": true
308308 }
309309 ```
310310
311311 No `exec` is needed when `theme.json` or `themes/theme.json` exists.
312-If `exec` is present, zot treats it as a normal extension too.
312+If `exec` is present, zut treats it as a normal extension too.
313313
314314 ## Validate
315315
316-zot theme files are plain JSON, not JSONC. Validate before installing:
316+zut theme files are plain JSON, not JSONC. Validate before installing:
317317
318318 ```bash
319319 python3 -m json.tool theme.json >/dev/null
R docs/zotfiles.md → docs/zutfiles.md +75 -75 renamed, 85% similar
@@ -1,8 +1,8 @@
1-# zotfile agents
1+# zutfile agents
22
3-A zotfile packages an agent's behavior into one portable `.zot` file. It can contain the agent's instructions, reusable skills, static assets, and metadata describing the runtime, model, operating-system, binary, and tool permissions it needs.
3+A zutfile packages an agent's behavior into one portable `.zut` file. It can contain the agent's instructions, reusable skills, static assets, and metadata describing the runtime, model, operating-system, binary, and tool permissions it needs.
44
5-The current implementation supports creating, inspecting, verifying, and running local directories and `.zot` archives. It can also run an agent repository or directory directly from any public GitHub repository without keeping a clone. zot has no built-in owner or official collection. Indexed registry distribution, installation, signatures, bundled executable extensions, network permissions, and environment permissions are not implemented yet.
5+The current implementation supports creating, inspecting, verifying, and running local directories and `.zut` archives. It can also run an agent repository or directory directly from any public GitHub repository without keeping a clone. zut has no built-in owner or official collection. Indexed registry distribution, installation, signatures, bundled executable extensions, network permissions, and environment permissions are not implemented yet.
66
77 ## Quick start
88
@@ -20,12 +20,12 @@ Add a minimal `manifest.json`:
2020
2121 ```json
2222 {
23- "zotfile": 1,
23+ "zutfile": 1,
2424 "name": "code-reviewer",
2525 "version": "0.1.0",
2626 "description": "Reviews a repository and reports actionable findings.",
2727 "runtime": {
28- "min_zot": "0.2.76"
28+ "min_zut": "0.2.76"
2929 },
3030 "model": {
3131 "requires": ["tools"],
@@ -65,20 +65,20 @@ summarize files that have no findings.
6565 Test the directory directly during development:
6666
6767 ```bash
68-zot inspect ./reviewer
69-zot run ./reviewer
70-zot run ./reviewer "Review the authentication package"
68+zut inspect ./reviewer
69+zut run ./reviewer
70+zut run ./reviewer "Review the authentication package"
7171 ```
7272
7373 Package it when ready:
7474
7575 ```bash
76-zot pack ./reviewer
77-# wrote code-reviewer.zot
76+zut pack ./reviewer
77+# wrote code-reviewer.zut
7878 # digest sha256:...
7979
80-zot verify ./code-reviewer.zot
81-zot run ./code-reviewer.zot
80+zut verify ./code-reviewer.zut
81+zut run ./code-reviewer.zut
8282 ```
8383
8484 The first run displays the declared permissions and asks for consent before the agent starts.
@@ -99,11 +99,11 @@ Only `manifest.json`. Other ordinary files and directories are included in the a
9999
100100 Do not include executable extensions in `extensions/`. The local runtime rejects any bundled extension directory containing `extension.json` because extension subprocesses cannot yet be confined to the manifest permissions. This is a deliberate fail-closed restriction.
101101
102-Symlinks are not supported and cause `zot pack` to fail.
102+Symlinks are not supported and cause `zut pack` to fail.
103103
104104 ## `AGENT.md`
105105
106-`AGENT.md` is optional. When present, it is appended to zot's system prompt by default. It should describe the agent's role, workflow, constraints, output format, and when to load bundled skills. When absent, it contributes no agent-specific text; other enabled global addenda still follow their normal rules.
106+`AGENT.md` is optional. When present, it is appended to zut's system prompt by default. It should describe the agent's role, workflow, constraints, output format, and when to load bundled skills. When absent, it contributes no agent-specific text; other enabled global addenda still follow their normal rules.
107107
108108 Keep capability and security declarations out of this file. Permissions come only from `manifest.json` and are enforced independently of agent-authored prose.
109109
@@ -115,11 +115,11 @@ Set `replace_system_prompt` to `true` in the manifest to use `AGENT.md` as the r
115115 }
116116 ```
117117
118-Replacement is intended for fully specialized agents. It replaces zot's built-in identity, while global append addenda such as enabled Ponytail coding guidance, project context, and skills retain their normal inclusion rules. The default layering behavior is usually preferable because it retains zot's normal identity and tool-use guidance.
118+Replacement is intended for fully specialized agents. It replaces zut's built-in identity, while global append addenda such as enabled Ponytail coding guidance, project context, and skills retain their normal inclusion rules. The default layering behavior is usually preferable because it retains zut's normal identity and tool-use guidance.
119119
120120 ## Bundled skills
121121
122-Place skills under `skills/<name>/SKILL.md` using the normal zot skill format:
122+Place skills under `skills/<name>/SKILL.md` using the normal zut skill format:
123123
124124 ```text
125125 skills/
@@ -141,7 +141,7 @@ description: Diagnose a failing command from its output and relevant source file
141141 4. Propose the smallest correction and validation plan.
142142 ```
143143
144-Bundled skills are added to normal skill discovery while the zotfile is running. The model sees their name and description in the skill manifest and can load the full body through the `skill` tool. See [skills.md](skills.md) for the complete skill format.
144+Bundled skills are added to normal skill discovery while the zutfile is running. The model sees their name and description in the skill manifest and can load the full body through the `skill` tool. See [skills.md](skills.md) for the complete skill format.
145145
146146 ## Manifest reference
147147
@@ -149,13 +149,13 @@ The current manifest shape is:
149149
150150 ```json
151151 {
152- "zotfile": 1,
152+ "zutfile": 1,
153153 "name": "code-reviewer",
154154 "version": "0.1.0",
155155 "description": "Reviews a repository and reports actionable findings.",
156156 "license": "MIT",
157157 "runtime": {
158- "min_zot": "0.2.76"
158+ "min_zut": "0.2.76"
159159 },
160160 "model": {
161161 "requires": ["tools", "reasoning"],
@@ -195,12 +195,12 @@ The current manifest shape is:
195195
196196 | Field | Required | Current behavior |
197197 |---|---:|---|
198-| `zotfile` | yes | Format version. Must be `1`. |
198+| `zutfile` | yes | Format version. Must be `1`. |
199199 | `name` | yes | Local agent identity and session/data namespace. See naming rules below. |
200200 | `version` | no | Displayed in consent and inspection output. Semver is recommended but not currently validated. |
201-| `description` | no | Displayed by `zot inspect`. |
201+| `description` | no | Displayed by `zut inspect`. |
202202 | `license` | no | Package metadata. Not otherwise interpreted by the local runtime. |
203-| `runtime.min_zot` | no | Minimum zot version. Older binaries refuse to run the agent. Unversioned development builds cannot satisfy a non-empty minimum. |
203+| `runtime.min_zut` | no | Minimum zut version. Older binaries refuse to run the agent. Unversioned development builds cannot satisfy a non-empty minimum. |
204204 | `model` | no | Model capabilities, minimum context, and preferences. |
205205 | `permissions` | no | Filesystem and bash permission ceiling. Omitted scopes deny access. |
206206 | `requirements` | no | Required operating systems and executables. |
@@ -244,7 +244,7 @@ Unknown capabilities are rejected. Use `tools`, not the draft spelling `tool_cal
244244 1. An explicitly selected model, if present, must satisfy the requirements.
245245 2. The user's configured default is retained if it satisfies them.
246246 3. The first compatible model in `preferred` is selected.
247-4. Otherwise, zot chooses a compatible active catalog model.
247+4. Otherwise, zut chooses a compatible active catalog model.
248248 5. If none qualifies, execution stops with an error.
249249
250250 `model.min_tier` is present in the format shape but is not supported by the local runtime. Leave it empty or omit it. A non-empty value is rejected.
@@ -271,11 +271,11 @@ Use `requirements.bin` for commands that must already be on `PATH`:
271271 }
272272 ```
273273
274-Zot checks these requirements before requesting consent. These zotfiles do not have install or postinstall hooks, so authors must document how users can obtain missing programs.
274+Zut checks these requirements before requesting consent. These zutfiles do not have install or postinstall hooks, so authors must document how users can obtain missing programs.
275275
276276 ### Entry fields
277277
278-`entry.pre` is auto-submitted once when `zot run` starts, before the initial prompt is applied. Values that begin with `!` use the interactive shell-escape path (or `BashTool` in `--print` / `--stream` / `--json`). Shell output streams live to the TUI (interactive) or stderr (non-interactive). Other values are sent as a normal user turn; in `--stream` mode that turn's assistant text also streams to stdout. After `pre` finishes, zot reloads extensions and rediscovers skills so anything installed by that command is available for the following turn.
278+`entry.pre` is auto-submitted once when `zut run` starts, before the initial prompt is applied. Values that begin with `!` use the interactive shell-escape path (or `BashTool` in `--print` / `--stream` / `--json`). Shell output streams live to the TUI (interactive) or stderr (non-interactive). Other values are sent as a normal user turn; in `--stream` mode that turn's assistant text also streams to stdout. After `pre` finishes, zut reloads extensions and rediscovers skills so anything installed by that command is available for the following turn.
279279
280280 ```json
281281 {
@@ -310,7 +310,7 @@ Two variables are available in filesystem scopes:
310310 | Variable | Resolves to |
311311 |---|---|
312312 | `${workspace}` | The current working directory used for the run. |
313-| `${agent_data}` | The agent's persistent private data directory under `$ZOT_HOME/agents/<name>/data/`. |
313+| `${agent_data}` | The agent's persistent private data directory under `$ZUT_HOME/agents/<name>/data/`. |
314314
315315 Relative paths are resolved beneath `${workspace}`. For example:
316316
@@ -412,7 +412,7 @@ In the current local runtime, `ask` is launch-time capability consent. It does n
412412 }
413413 ```
414414
415-Every command in a shell expression must be listed. Zot checks commands separated by `;`, `&&`, `||`, and pipes. Paths are reduced to their base command name, so `/usr/bin/git` is checked as `git`. A leading environment assignment is skipped when identifying the command.
415+Every command in a shell expression must be listed. Zut checks commands separated by `;`, `&&`, `||`, and pipes. Paths are reduced to their base command name, so `/usr/bin/git` is checked as `git`. A leading environment assignment is skipped when identifying the command.
416416
417417 The allowlist rejects shell substitution, newlines, redirection, backticks, `$()`, `<`, and `>`. This intentionally supports simple command pipelines rather than arbitrary shell programs.
418418
@@ -439,7 +439,7 @@ Bundled executable extensions are rejected for the same reason. Network filterin
439439
440440 ## Consent and persistent data
441441
442-Before the first run, zot prints the agent identity and expanded filesystem and bash permissions, then asks:
442+Before the first run, zut prints the agent identity and expanded filesystem and bash permissions, then asks:
443443
444444 ```text
445445 Agent code-reviewer@0.1.0 wants to run.
@@ -454,7 +454,7 @@ Allow? [y/N]
454454 For modes other than `bash: ask`, approval is cached for the exact artifact digest under:
455455
456456 ```text
457-$ZOT_HOME/agents/<name>/consents/<digest>.json
457+$ZUT_HOME/agents/<name>/consents/<digest>.json
458458 ```
459459
460460 Any change to the packaged artifact produces a different digest and requires consent again. `bash: ask` always requires fresh launch consent.
@@ -462,11 +462,11 @@ Any change to the packaged artifact produces a different digest and requires con
462462 Non-interactive runs refuse to bypass consent by default. For controlled automation, pass `-y` / `--yes` or set:
463463
464464 ```bash
465-zot run -y ./code-reviewer.zot --print "Review this repository"
465+zut run -y ./code-reviewer.zut --print "Review this repository"
466466 ```
467467
468468 ```bash
469-ZOT_AGENT_CONSENT=1 zot run ./code-reviewer.zot --print "Review this repository"
469+ZUT_AGENT_CONSENT=1 zut run ./code-reviewer.zut --print "Review this repository"
470470 ```
471471
472472 This environment variable skips the consent prompt. Only use it after independently inspecting and trusting the exact artifact being run.
@@ -474,116 +474,116 @@ This environment variable skips the consent prompt. Only use it after independen
474474 Persistent agent data lives under:
475475
476476 ```text
477-$ZOT_HOME/agents/<name>/data/
477+$ZUT_HOME/agents/<name>/data/
478478 ```
479479
480480 The directory is created for every run, but the agent can access it only when `${agent_data}` appears in the relevant filesystem permission scope.
481481
482482 ## Agent-scoped sessions
483483
484-Sessions created by a zotfile are isolated from ordinary zot sessions and from other agents:
484+Sessions created by a zutfile are isolated from ordinary zut sessions and from other agents:
485485
486486 ```text
487-$ZOT_HOME/sessions/agents/<name>/
487+$ZUT_HOME/sessions/agents/<name>/
488488 ```
489489
490490 Normal session flags still apply, including `--continue`, `--resume`, `--session`, and `--no-session`, but their default storage root is scoped to the active agent. The interactive `/sessions` picker and `/session` import, fork, and tree commands use that same agent-scoped root; `/fork` is the direct alias for selecting a user message and branching after that turn. `/session tree` shows the current session's family within that root (its ancestor and descendants), keeps pre-compaction user and assistant history available for checkout, and hides tool/internal rows from the fork picker. Tree-navigation branches stay hidden from the flat `/sessions` picker. Resuming honors the selected session's stored provider/model pair; a failed provider/model rebuild leaves the current session unchanged.
491491
492492 ## Commands
493493
494-### `zot run`
494+### `zut run`
495495
496496 Run an unpackaged directory during development:
497497
498498 ```bash
499-zot run ./my-agent
500-zot run -y ./my-agent
501-zot run ./my-agent "Do the task"
502-zot run ./my-agent -y --print "Do the task"
499+zut run ./my-agent
500+zut run -y ./my-agent
501+zut run ./my-agent "Do the task"
502+zut run ./my-agent -y --print "Do the task"
503503 ```
504504
505505 Run a packed artifact:
506506
507507 ```bash
508-zot run ./my-agent.zot
509-zot run ./my-agent.zot --print "Do the task"
510-zot run ./my-agent.zot --stream "Do the task"
511-zot run ./my-agent.zot --json "Do the task"
508+zut run ./my-agent.zut
509+zut run ./my-agent.zut --print "Do the task"
510+zut run ./my-agent.zut --stream "Do the task"
511+zut run ./my-agent.zut --json "Do the task"
512512 ```
513513
514-Arguments after the reference use zot's normal CLI parser, so model, provider, reasoning, cwd, session, tool, and output-mode flags remain available. The manifest still imposes its permission and compatibility ceiling. In interactive mode, enabling **show loaded resources at startup** in `/settings` displays the manifest name in an `[Agent]` section above the transcript.
514+Arguments after the reference use zut's normal CLI parser, so model, provider, reasoning, cwd, session, tool, and output-mode flags remain available. The manifest still imposes its permission and compatibility ceiling. In interactive mode, enabling **show loaded resources at startup** in `/settings` displays the manifest name in an `[Agent]` section above the transcript.
515515
516-Local filesystem directories, local archive paths, GitHub shorthand, and public GitHub agent-directory URLs are accepted. A single-part name resolves only to a matching local directory or `.zot` archive:
516+Local filesystem directories, local archive paths, GitHub shorthand, and public GitHub agent-directory URLs are accepted. A single-part name resolves only to a matching local directory or `.zut` archive:
517517
518518 ```bash
519-zot run my-agent
519+zut run my-agent
520520 ```
521521
522522 A two-part name is treated as a GitHub `owner/repository` reference. Resolution remains local-first, then uses the repository root:
523523
524524 ```bash
525-zot run frkr/zot-archify
525+zut run frkr/zot-archify
526526 ```
527527
528528 Use three or more parts to select an agent directory from any public GitHub collection:
529529
530530 ```bash
531-zot run acme/agents/reviewer
532-zot run acme/clankers/reviewers/go
531+zut run acme/agents/reviewer
532+zut run acme/clankers/reviewers/go
533533 ```
534534
535-There is no built-in owner, official collection, collection configuration, or allowlist. Every shorthand part must use lowercase letters, digits, dots, hyphens, or underscores. Backslashes are also accepted as separators on Windows. Prefix a reference with `./`, use an absolute path, retain the `.zot` suffix, or provide a complete GitHub URL to force local or explicit remote resolution.
535+There is no built-in owner, official collection, collection configuration, or allowlist. Every shorthand part must use lowercase letters, digits, dots, hyphens, or underscores. Backslashes are also accepted as separators on Windows. Prefix a reference with `./`, use an absolute path, retain the `.zut` suffix, or provide a complete GitHub URL to force local or explicit remote resolution.
536536
537-For GitHub, zot downloads the repository archive into a temporary directory, selects the requested agent subdirectory, validates it, runs it, and removes the downloaded files when the command exits:
537+For GitHub, zut downloads the repository archive into a temporary directory, selects the requested agent subdirectory, validates it, runs it, and removes the downloaded files when the command exits:
538538
539539 ```bash
540-zot run https://github.com/acme/agents/reviewer
540+zut run https://github.com/acme/agents/reviewer
541541 ```
542542
543543 A standard GitHub tree URL is also supported:
544544
545545 ```bash
546-zot run https://github.com/acme/agents/tree/main/reviewer
546+zut run https://github.com/acme/agents/tree/main/reviewer
547547 ```
548548
549-Short forms read the repository's default branch through GitHub's `HEAD` archive. A tree URL uses the branch or tag in the URL. Private repositories and branch or tag names containing `/` are not currently supported. The downloaded source is temporary, but normal agent data, consent receipts, and session transcripts remain under `$ZOT_HOME`.
549+Short forms read the repository's default branch through GitHub's `HEAD` archive. A tree URL uses the branch or tag in the URL. Private repositories and branch or tag names containing `/` are not currently supported. The downloaded source is temporary, but normal agent data, consent receipts, and session transcripts remain under `$ZUT_HOME`.
550550
551551 `owner/repository` and `owner/repository/path` are direct GitHub mappings, not an indexed or signed registry. Installed names, arbitrary URLs, and OCI references are not resolved yet.
552552
553-### `zot pack`
553+### `zut pack`
554554
555555 ```bash
556-zot pack [directory] [output]
556+zut pack [directory] [output]
557557 ```
558558
559559 Examples:
560560
561561 ```bash
562-zot pack
563-zot pack ./my-agent
564-zot pack ./my-agent ./dist/reviewer.zot
562+zut pack
563+zut pack ./my-agent
564+zut pack ./my-agent ./dist/reviewer.zut
565565 ```
566566
567-The default directory is the current directory. When output is omitted, zot uses `<manifest-name>.zot`. If the output has no `.zot` suffix, zot adds one.
567+The default directory is the current directory. When output is omitted, zut uses `<manifest-name>.zut`. If the output has no `.zut` suffix, zut adds one.
568568
569-Packing validates the manifest and directory, then creates a zstd-compressed tar archive. Entries are sorted and tar metadata is normalized with fixed timestamps and numeric ownership. The output archive itself is excluded when it is located inside the source directory. Zot prints the SHA-256 digest of the resulting compressed artifact.
569+Packing validates the manifest and directory, then creates a zstd-compressed tar archive. Entries are sorted and tar metadata is normalized with fixed timestamps and numeric ownership. The output archive itself is excluded when it is located inside the source directory. Zut prints the SHA-256 digest of the resulting compressed artifact.
570570
571-### `zot inspect`
571+### `zut inspect`
572572
573573 ```bash
574-zot inspect ./my-agent
575-zot inspect ./my-agent.zot
576-zot inspect https://github.com/acme/agents/code-reviewer
574+zut inspect ./my-agent
575+zut inspect ./my-agent.zut
576+zut inspect https://github.com/acme/agents/code-reviewer
577577 ```
578578
579579 Inspection validates and prints the agent's name, version, description, digest, declared permissions, and complete file list. It does not execute the agent.
580580
581-For a directory, the digest is computed from its canonical uncompressed tar representation. For a `.zot` archive, the digest is computed from the archive bytes. Therefore a source-directory digest and its packed-archive digest are not expected to match.
581+For a directory, the digest is computed from its canonical uncompressed tar representation. For a `.zut` archive, the digest is computed from the archive bytes. Therefore a source-directory digest and its packed-archive digest are not expected to match.
582582
583-### `zot verify`
583+### `zut verify`
584584
585585 ```bash
586-zot verify ./my-agent.zot
586+zut verify ./my-agent.zut
587587 ```
588588
589589 The local `verify` command validates that the archive can be safely loaded, validates the manifest and required layout, and prints its SHA-256 digest.
@@ -596,7 +596,7 @@ The archive loader applies these limits:
596596
597597 | Limit | Value |
598598 |---|---:|
599-| Compressed `.zot` file | 100 MiB |
599+| Compressed `.zut` file | 100 MiB |
600600 | One extracted entry | 64 MiB |
601601 | Total extracted content | 256 MiB |
602602
@@ -607,7 +607,7 @@ Archive extraction rejects absolute paths and parent traversal. Unsupported tar
607607 1. Start with the narrowest permissions possible. A reviewer usually needs repository reads, not writes or bash.
608608 2. Keep `AGENT.md` focused on stable behavior. Put task-specific procedures in skills so the model loads them only when relevant.
609609 3. Declare every external command in both `requirements.bin` and the bash allowlist when the agent must execute it.
610-4. Test from a directory first with `zot inspect` and `zot run`.
610+4. Test from a directory first with `zut inspect` and `zut run`.
611611 5. Test denied behavior as well as allowed behavior. Ask the agent to attempt an out-of-scope read, write, and command.
612612 6. Pack the same source twice and compare behavior and output digests before distributing it.
613613 7. Inspect and verify the final archive, not only its source directory.
@@ -617,11 +617,11 @@ Archive extraction rejects absolute paths and parent traversal. Unsupported tar
617617
618618 Implemented now:
619619
620-- local directories and `.zot` archives
620+- local directories and `.zut` archives
621621 - local-only resolution for single-part names
622622 - local-first `owner/repository` and `owner/repository/path` resolution to any public GitHub repository
623623 - temporary execution of agent directories from public GitHub repositories
624-- `zot pack`, `zot inspect`, `zot verify`, and `zot run`
624+- `zut pack`, `zut inspect`, `zut verify`, and `zut run`
625625 - canonical tar creation with zstd compression
626626 - archive digest reporting and safe extraction limits
627627 - `AGENT.md` append or replacement behavior
@@ -636,8 +636,8 @@ Implemented now:
636636
637637 Not implemented yet:
638638
639-- `zot install`, `zot agents`, `zot use`, `zot update`, or `zot publish`
640-- installed-name, arbitrary URL, OCI, configurable registry, or zot.sh index resolution
639+- `zut install`, `zut agents`, `zut use`, `zut update`, or `zut publish`
640+- installed-name, arbitrary URL, OCI, configurable registry, or GitHub index resolution
641641 - network allowlist enforcement
642642 - environment-variable filtering
643643 - safely confined bundled executable extensions
@@ -646,4 +646,4 @@ Not implemented yet:
646646 - per-agent Telegram, bot, or RPC selection
647647 - `--no-sandbox`, `--trust-bash`, or registry trust override flags
648648
649-Treat the unsupported fields and commands in the broader zotfile proposal as forward-looking design, not as current runtime behavior.
649+Treat the unsupported fields and commands in the broader zutfile proposal as forward-looking design, not as current runtime behavior.
M examples/extensions/approve/README.md +3 -3
@@ -1,4 +1,4 @@
1-# approve — example zot extension
1+# approve — example zut extension
22
33 Demonstrates the **spontaneous `open_panel`** pattern: an
44 extension-registered tool opens a panel from inside its handler
@@ -39,12 +39,12 @@ go build -o approve .
3939 ## Install
4040
4141 ```bash
42-zot ext install .
42+zut ext install .
4343 ```
4444
4545 ## Try it
4646
47-In zot, ask:
47+In zut, ask:
4848
4949 > Request approval to delete the temp directory.
5050
M examples/extensions/approve/main.go +3 -3
@@ -16,9 +16,9 @@
1616 //
1717 // Install:
1818 //
19-// zot ext install .
19+// zut ext install .
2020 //
21-// Try it — ask zot something like:
21+// Try it — ask zut something like:
2222 //
2323 // "Request approval to delete the temp directory."
2424 //
@@ -35,7 +35,7 @@ import (
3535 "strings"
3636 "sync"
3737
38- "github.com/patriceckhart/zot/packages/agent/ext"
38+ "github.com/bnema/zut/packages/agent/ext"
3939 )
4040
4141 const schema = `{
M examples/extensions/clock/README.md +6 -6
@@ -1,4 +1,4 @@
1-# clock — example zot extension (Node, no dependencies)
1+# clock — example zut extension (Node, no dependencies)
22
33 A minimal TypeScript-style extension showing the wire protocol from
44 the Node side without any SDK. Pure stdlib (`readline`, `process`).
@@ -12,15 +12,15 @@ Node 18 or newer (uses ESM). No `npm install` step.
1212 From this directory:
1313
1414 ```bash
15-zot ext install .
15+zut ext install .
1616 ```
1717
18-This copies the manifest + script into `$ZOT_HOME/extensions/clock/`.
19-zot picks it up the next time you launch the TUI.
18+This copies the manifest + script into `$ZUT_HOME/extensions/clock/`.
19+zut picks it up the next time you launch the TUI.
2020
2121 ## Use
2222
23-In zot:
23+In zut:
2424
2525 - `/now` — extension pushes a styled note showing local
2626 and ISO time (no model call)
@@ -44,7 +44,7 @@ authoring works too — rename `index.js` → `index.ts`, install
4444 }
4545 ```
4646
47-zot doesn't care which one you use; it just spawns whatever `exec`
47+zut doesn't care which one you use; it just spawns whatever `exec`
4848 points at and reads/writes JSON lines on its stdio.
4949
5050 ## See also
M examples/extensions/clock/index.js +15 -8
@@ -1,4 +1,4 @@
1-// clock — a zot extension written in plain Node (no dependencies).
1+// clock — a zut extension written in plain Node (no dependencies).
22 //
33 // Registers two slash commands:
44 // /now — pushes the current local time into the chat as
@@ -15,9 +15,9 @@
1515 // stdout.
1616 //
1717 // Install:
18-// zot ext install /path/to/this/dir
18+// zut ext install /path/to/this/dir
1919 //
20-// Then in zot:
20+// Then in zut:
2121 // /now
2222 // /uptime
2323
@@ -31,7 +31,7 @@ const STARTED_AT = Date.now();
3131 /** @typedef {{type: string, id?: string, [k: string]: unknown}} Frame */
3232
3333 /**
34- * Send a frame to zot. One JSON object per line; flush immediately
34+ * Send a frame to zut. One JSON object per line; flush immediately
3535 * so the host doesn't sit waiting on a buffer.
3636 * @param {Frame} obj
3737 */
@@ -40,7 +40,7 @@ function send(obj) {
4040 }
4141
4242 /**
43- * stderr is captured by zot to $ZOT_HOME/logs/ext-clock.log; perfect
43+ * stderr is captured by zut to $ZUT_HOME/logs/ext-clock.log; perfect
4444 * for debug output. Anything written to stdout would corrupt the
4545 * protocol stream.
4646 * @param {string} msg
@@ -52,6 +52,7 @@ function log(msg) {
5252 // 1. Hello first.
5353 send({
5454 type: "hello",
55+ protocol_version: 2,
5556 name: NAME,
5657 version: VERSION,
5758 capabilities: ["commands"],
@@ -69,7 +70,7 @@ send({
6970 description: "ask the agent to riff on how long the clock ext has run",
7071 });
7172
72-// 3. Read frames until stdin closes (zot shuts us down).
73+// 3. Read frames until stdin closes (zut shuts us down).
7374 const rl = createInterface({ input: stdin, crlfDelay: Infinity });
7475
7576 rl.on("line", (line) => {
@@ -84,8 +85,14 @@ rl.on("line", (line) => {
8485
8586 switch (frame.type) {
8687 case "hello_ack":
88+ if (frame.protocol_version !== 2) {
89+ log(`unsupported protocol version: ${frame.protocol_version}`);
90+ process.exitCode = 1;
91+ rl.close();
92+ break;
93+ }
8794 log(
88- `connected to zot ${frame.zot_version} (${frame.provider}/${frame.model})`,
95+ `connected to zut ${frame.zut_version} (${frame.provider}/${frame.model})`,
8996 );
9097 break;
9198
@@ -147,7 +154,7 @@ function handleCommand(frame) {
147154 id,
148155 action: "prompt",
149156 prompt:
150- `The clock extension has been running for ${seconds}s in this zot session. ` +
157+ `The clock extension has been running for ${seconds}s in this zut session. ` +
151158 `Riff on that briefly in one short sentence — be a little dramatic. ${focus}`.trim(),
152159 });
153160 return;
M examples/extensions/guard/README.md +5 -5
@@ -1,4 +1,4 @@
1-# guard — example zot extension (Go, phase 3)
1+# guard — example zut extension (Go, phase 3)
22
33 Demonstrates the **event subscription** and **tool-call interception**
44 half of the extension protocol (phase 3).
@@ -6,7 +6,7 @@ half of the extension protocol (phase 3).
66 What it does:
77
88 - Subscribes to `session_start`, `turn_start`, `tool_call`, `turn_end`
9- and appends a line to `/tmp/zot-guard-audit.log` for each.
9+ and appends a line to `/tmp/zut-guard-audit.log` for each.
1010 - Intercepts every `bash` tool call. If the command matches a danger
1111 regex (`rm -rf`, `sudo`, `dd of=/`, `mkfs`, the fork bomb, `chmod -R
1212 777`), the call is **refused**. The model sees the refusal as the
@@ -23,12 +23,12 @@ go build -o guard .
2323 ## Install
2424
2525 ```bash
26-zot ext install .
26+zut ext install .
2727 ```
2828
2929 ## Try it
3030
31-In zot, ask:
31+In zut, ask:
3232
3333 > Run `rm -rf /tmp/foo`
3434
@@ -42,7 +42,7 @@ Allowed; the audit log records the call.
4242 Tail the audit log:
4343
4444 ```bash
45-tail -f /tmp/zot-guard-audit.log
45+tail -f /tmp/zut-guard-audit.log
4646 ```
4747
4848 ## Extending the danger list
M examples/extensions/guard/main.go +4 -4
@@ -15,8 +15,8 @@
1515 // cd examples/extensions/guard
1616 // go build -o guard .
1717 //
18-// Drop it next to its extension.json under $ZOT_HOME/extensions/guard/
19-// (or `zot ext install ./guard` from this directory) and the next zot
18+// Drop it next to its extension.json under $ZUT_HOME/extensions/guard/
19+// (or `zut ext install ./guard` from this directory) and the next zut
2020 // session will load it automatically.
2121 package main
2222
@@ -29,7 +29,7 @@ import (
2929 "strings"
3030 "time"
3131
32- "github.com/patriceckhart/zot/packages/agent/ext"
32+ "github.com/bnema/zut/packages/agent/ext"
3333 )
3434
3535 var dangerPatterns = []*regexp.Regexp{
@@ -44,7 +44,7 @@ var dangerPatterns = []*regexp.Regexp{
4444 func main() {
4545 e := ext.New("guard", "1.0.0")
4646
47- auditPath := filepath.Join(os.TempDir(), "zot-guard-audit.log")
47+ auditPath := filepath.Join(os.TempDir(), "zut-guard-audit.log")
4848 auditFile, _ := os.OpenFile(auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
4949 if auditFile != nil {
5050 defer auditFile.Close()
M examples/extensions/hello/README.md +4 -4
@@ -1,4 +1,4 @@
1-# hello — example zot extension (Go)
1+# hello — example zut extension (Go)
22
33 A minimal example showing how to register slash commands with the
44 `ext` SDK.
@@ -14,16 +14,16 @@ go build -o hello .
1414
1515 ```bash
1616 # from this directory
17-zot ext install .
17+zut ext install .
1818 ```
1919
2020 This copies the directory (manifest + binary) into
21-`$ZOT_HOME/extensions/hello/`. zot picks it up the next time you
21+`$ZUT_HOME/extensions/hello/`. zut picks it up the next time you
2222 launch the TUI.
2323
2424 ## Use
2525
26-In zot, type:
26+In zut, type:
2727
2828 - `/hello` — agent greets you
2929 - `/hello <name>` — agent greets `<name>`
M examples/extensions/hello/main.go +3 -3
@@ -1,4 +1,4 @@
1-// hello — a tiny zot extension that registers /hello and /summon.
1+// hello — a tiny zut extension that registers /hello and /summon.
22 //
33 // Build it:
44 //
@@ -6,14 +6,14 @@
66 // go build -o hello .
77 //
88 // Then drop it next to its extension.json under
9-// $ZOT_HOME/extensions/hello/, or run `zot ext install ./hello`
9+// $ZUT_HOME/extensions/hello/, or run `zut ext install ./hello`
1010 // from this directory.
1111 package main
1212
1313 import (
1414 "strings"
1515
16- "github.com/patriceckhart/zot/packages/agent/ext"
16+ "github.com/bnema/zut/packages/agent/ext"
1717 )
1818
1919 func main() {
M examples/extensions/mcp-bridge/Makefile +10 -10
@@ -3,13 +3,13 @@
33 BIN := $(notdir $(CURDIR))
44 # Must match "name" in extension.json (slash command/logical extension name).
55 EXT := mcp
6-ZOT_HOME ?= $(shell \
6+ZUT_HOME ?= $(shell \
77 if [ -n "$$XDG_STATE_HOME" ]; then \
8- printf '%s/zot' "$$XDG_STATE_HOME"; \
8+ printf '%s/zut' "$$XDG_STATE_HOME"; \
99 elif [ "$$(uname -s)" = "Darwin" ]; then \
10- printf '%s/Library/Application Support/zot' "$$HOME"; \
10+ printf '%s/Library/Application Support/zut' "$$HOME"; \
1111 else \
12- printf '%s/.local/state/zot' "$$HOME"; \
12+ printf '%s/.local/state/zut' "$$HOME"; \
1313 fi)
1414
1515 default: install
@@ -18,11 +18,11 @@ build:
1818 go build -o $(BIN) .
1919
2020 install:
21- -zot ext remove $(EXT) --yes
22- rm -rf "$(ZOT_HOME)/extensions/$(BIN)"
23- mkdir -p "$(ZOT_HOME)/extensions/$(EXT)"
24- cp extension.json "$(ZOT_HOME)/extensions/$(EXT)/extension.json"
25- go build -o "$(ZOT_HOME)/extensions/$(EXT)/$(BIN)" .
21+ -zut ext remove $(EXT) --yes
22+ rm -rf "$(ZUT_HOME)/extensions/$(BIN)"
23+ mkdir -p "$(ZUT_HOME)/extensions/$(EXT)"
24+ cp extension.json "$(ZUT_HOME)/extensions/$(EXT)/extension.json"
25+ go build -o "$(ZUT_HOME)/extensions/$(EXT)/$(BIN)" .
2626
2727 uninstall:
28- -zot ext remove $(EXT) --yes
28+ -zut ext remove $(EXT) --yes
M examples/extensions/mcp-bridge/README.md +17 -17
@@ -1,8 +1,8 @@
11 # mcp-bridge
22
3-Connect zot to [MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers.
3+Connect zut to [MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers.
44
5-This extension reads MCP server configurations from standard locations (same format as Claude Desktop, Cursor, Cline, etc.) and bridges their tools into zot so the LLM can call them directly.
5+This extension reads MCP server configurations from standard locations (same format as Claude Desktop, Cursor, Cline, etc.) and bridges their tools into zut so the LLM can call them directly.
66
77 ## Features
88
@@ -31,8 +31,8 @@ This extension reads MCP server configurations from standard locations (same for
3131 2. **Create a project config file:**
3232
3333 ```bash
34- mkdir -p .zot
35- cat > .zot/mcp.json << 'EOF'
34+ mkdir -p .zut
35+ cat > .zut/mcp.json << 'EOF'
3636 {
3737 "mcpServers": {
3838 "filesystem": {
@@ -51,10 +51,10 @@ This extension reads MCP server configurations from standard locations (same for
5151 3. **Install the extension:**
5252
5353 ```bash
54- zot ext install .
54+ zut ext install .
5555 ```
5656
57-4. **Restart zot.** On first run the extension refreshes its tool cache in the background. When zot shows `MCP tool cache changed`, run `/reload-ext` once. Future launches register the cached MCP tools immediately as deferred definitions.
57+4. **Restart zut.** On first run the extension refreshes its tool cache in the background. When zut shows `MCP tool cache changed`, run `/reload-ext` once. Future launches register the cached MCP tools immediately as deferred definitions.
5858
5959 The model initially sees one small loader tool, `mcp__search_tools`. It searches cached MCP tool names and descriptions locally, activates up to eight relevant definitions by default, and then calls the selected MCP tool normally. This keeps large MCP installations compatible with providers that limit request or tool-schema size.
6060
@@ -64,12 +64,12 @@ Config files are loaded from two locations (project overrides global per-server)
6464
6565 | Location | Scope |
6666 |---|---|
67-| `$ZOT_HOME/mcp.json` | Global (`$XDG_STATE_HOME/zot/mcp.json` when `XDG_STATE_HOME` is set) |
68-| `.zot/mcp.json` | Project-level (in your project root) |
67+| `$ZUT_HOME/mcp.json` | Global (`$XDG_STATE_HOME/zut/mcp.json` when `XDG_STATE_HOME` is set) |
68+| `.zut/mcp.json` | Project-level (in your project root) |
6969
7070 ### Config Format
7171
72-Standard MCP config — same as Claude Desktop, with zot-specific extensions:
72+Standard MCP config — same as Claude Desktop, with zut-specific extensions:
7373
7474 ```jsonc
7575 {
@@ -154,7 +154,7 @@ Standard MCP config — same as Claude Desktop, with zot-specific extensions:
154154
155155 ```
156156 ┌──────────────────────────────────────────────────────────────┐
157-│ zot agent │
157+│ zut agent │
158158 │ │
159159 │ ┌──────────┐ tool_call ┌──────────────┐ │
160160 │ │ LLM │───────────────▶│ mcp-bridge │ │
@@ -173,7 +173,7 @@ Standard MCP config — same as Claude Desktop, with zot-specific extensions:
173173
174174 1. **Startup**: mcp-bridge reads config and registers tools from `mcp-tools-cache.json`
175175 2. **Background refresh**: starts configured MCP servers, calls `tools/list`, and updates the cache when definitions change
176-3. **Reload**: if the cache changed, run `/reload-ext` once so zot rebuilds the tool registry with the new definitions
176+3. **Reload**: if the cache changed, run `/reload-ext` once so zut rebuilds the tool registry with the new definitions
177177 4. **Naming**: tools appear as `mcp__<server>__<tool>` (e.g., `mcp__filesystem__read_file`)
178178 5. **Idle timeout**: servers not used for 5 minutes are automatically stopped
179179 6. **Auto-respawn**: calling a tool on a stopped server wakes it up
@@ -196,7 +196,7 @@ Standard MCP config — same as Claude Desktop, with zot-specific extensions:
196196
197197 ## Tool Naming
198198
199-Tools are namespaced to avoid collisions with zot's built-in tools:
199+Tools are namespaced to avoid collisions with zut's built-in tools:
200200
201201 ```
202202 mcp__<server>__<tool>
@@ -214,7 +214,7 @@ Server and tool names are sanitized (non-alphanumeric characters become `_`).
214214
215215 The bridge uses a "smart lazy" strategy:
216216
217-1. **On startup**: cached tool definitions are registered without blocking zot startup
217+1. **On startup**: cached tool definitions are registered without blocking zut startup
218218 2. **In the background**: servers start long enough to refresh the tool cache
219219 3. **During use**: servers stay running for fast tool calls
220220 4. **After 5 min idle**: unused servers are automatically stopped (saves memory/CPU)
@@ -235,7 +235,7 @@ This gives you:
235235
236236 **View extension logs:**
237237 ```bash
238-zot ext logs mcp -f
238+zut ext logs mcp -f
239239 ```
240240
241241 **Common issues:**
@@ -261,11 +261,11 @@ go build -o mcp-bridge .
261261 go test ./...
262262 go vet ./...
263263
264-# Run without installing (for one zot session)
265-zot --ext .
264+# Run without installing (for one zut session)
265+zut --ext .
266266
267267 # View logs
268-zot ext logs mcp -f
268+zut ext logs mcp -f
269269 ```
270270
271271 ## License
M examples/extensions/mcp-bridge/bridge.go +30 -30
@@ -1,11 +1,11 @@
1-// bridge.go — MCP tool → zot tool registration and routing.
1+// bridge.go — MCP tool → zut tool registration and routing.
22 //
3-// Converts MCP tools into zot-registered tools with namespaced names:
3+// Converts MCP tools into zut-registered tools with namespaced names:
44 //
55 // mcp__<server>__<tool>
66 //
77 // The double underscore separates the server name from the tool name,
8-// avoiding collisions with zot's built-in tools (read, write, edit, bash, lsp, skill).
8+// avoiding collisions with zut's built-in tools (read, write, edit, bash, lsp, skill).
99 package main
1010
1111 import (
@@ -20,8 +20,8 @@ import (
2020 "sync"
2121 "time"
2222
23+ "github.com/bnema/zut/packages/agent/ext"
2324 "github.com/mark3labs/mcp-go/mcp"
24- "github.com/patriceckhart/zot/packages/agent/ext"
2525 )
2626
2727 const (
@@ -48,14 +48,14 @@ var mcpSearchToolSchema = json.RawMessage(`{
4848 "additionalProperties": false
4949 }`)
5050
51-// toolMapping tracks which zot tool name maps to which MCP server + tool.
51+// toolMapping tracks which zut tool name maps to which MCP server + tool.
5252 type toolMapping struct {
5353 serverName string // e.g. "filesystem"
5454 mcpTool string // e.g. "read_file"
5555 description string
5656 }
5757
58-// bridge connects MCP servers to zot's extension protocol.
58+// bridge connects MCP servers to zut's extension protocol.
5959 type bridge struct {
6060 e *ext.Extension
6161 cwd string
@@ -64,14 +64,14 @@ type bridge struct {
6464 // the lock-free reads in handleToolCall and the idle reaper safe.
6565 // Do not add or remove entries after startup.
6666 servers map[string]*managedServer
67- mapping map[string]toolMapping // zot tool name → MCP server + tool; guarded by mu
67+ mapping map[string]toolMapping // zut tool name → MCP server + tool; guarded by mu
6868 logger *log.Logger
6969
7070 mu sync.Mutex // guards mapping and searchRegistered
7171 searchRegistered bool
7272 }
7373
74-// newBridge creates a new MCP→zot bridge.
74+// newBridge creates a new MCP→zut bridge.
7575 func newBridge(e *ext.Extension, cwd string, logger *log.Logger) *bridge {
7676 return &bridge{
7777 e: e,
@@ -82,8 +82,8 @@ func newBridge(e *ext.Extension, cwd string, logger *log.Logger) *bridge {
8282 }
8383 }
8484
85-// sanitizeName converts a string into a valid zot tool name component.
86-// Zot tool names must match [a-zA-Z][a-zA-Z0-9_]*.
85+// sanitizeName converts a string into a valid zut tool name component.
86+// Zut tool names must match [a-zA-Z][a-zA-Z0-9_]*.
8787 var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9]`)
8888
8989 func sanitizeName(s string) string {
@@ -98,7 +98,7 @@ func sanitizeName(s string) string {
9898 return s
9999 }
100100
101-// toolName builds the namespaced zot tool name for an MCP tool.
101+// toolName builds the namespaced zut tool name for an MCP tool.
102102 func toolName(serverName, mcpToolName string) string {
103103 return fmt.Sprintf("mcp__%s__%s", sanitizeName(serverName), sanitizeName(mcpToolName))
104104 }
@@ -222,29 +222,29 @@ func (b *bridge) refreshToolCache(ctx context.Context, path string) (bool, error
222222 }
223223
224224 func (b *bridge) registerCachedTool(serverName string, tool cachedTool) {
225- zotName := toolName(serverName, tool.Name)
225+ zutName := toolName(serverName, tool.Name)
226226
227227 b.mu.Lock()
228- if existing, exists := b.mapping[zotName]; exists {
228+ if existing, exists := b.mapping[zutName]; exists {
229229 b.mu.Unlock()
230230 if existing.serverName != serverName || existing.mcpTool != tool.Name {
231231 b.logger.Printf("tool name collision: %s already maps to %s/%s; skipping %s/%s",
232- zotName, existing.serverName, existing.mcpTool, serverName, tool.Name)
232+ zutName, existing.serverName, existing.mcpTool, serverName, tool.Name)
233233 }
234234 return
235235 }
236- b.mapping[zotName] = toolMapping{
236+ b.mapping[zutName] = toolMapping{
237237 serverName: serverName,
238238 mcpTool: tool.Name,
239239 description: tool.Description,
240240 }
241241 b.mu.Unlock()
242242
243- b.e.DeferredTool(zotName, tool.Description, json.RawMessage(tool.Schema), func(args json.RawMessage) ext.ToolResult {
244- return b.handleToolCall(zotName, args)
243+ b.e.DeferredTool(zutName, tool.Description, json.RawMessage(tool.Schema), func(args json.RawMessage) ext.ToolResult {
244+ return b.handleToolCall(zutName, args)
245245 })
246246
247- b.logger.Printf("registered deferred cached tool: %s → %s/%s", zotName, serverName, tool.Name)
247+ b.logger.Printf("registered deferred cached tool: %s → %s/%s", zutName, serverName, tool.Name)
248248 }
249249
250250 type toolSearchArgs struct {
@@ -326,8 +326,8 @@ func (b *bridge) findTools(query string, limit int) []toolSearchMatch {
326326 return matches
327327 }
328328
329-func scoreToolMatch(terms []string, zotName string, mapping toolMapping) int {
330- name := strings.ToLower(zotName)
329+func scoreToolMatch(terms []string, zutName string, mapping toolMapping) int {
330+ name := strings.ToLower(zutName)
331331 server := strings.ToLower(mapping.serverName)
332332 mcpName := strings.ToLower(mapping.mcpTool)
333333 description := strings.ToLower(mapping.description)
@@ -408,17 +408,17 @@ func mcpToolSchema(tool mcp.Tool) any {
408408 return schema
409409 }
410410
411-// handleToolCall routes a zot tool call to the appropriate MCP server.
412-func (b *bridge) handleToolCall(zotName string, args json.RawMessage) ext.ToolResult {
411+// handleToolCall routes a zut tool call to the appropriate MCP server.
412+func (b *bridge) handleToolCall(zutName string, args json.RawMessage) ext.ToolResult {
413413 b.mu.Lock()
414- mapping, ok := b.mapping[zotName]
414+ mapping, ok := b.mapping[zutName]
415415 b.mu.Unlock()
416416
417417 if !ok {
418418 return ext.TextErrorResult(fmt.Sprintf(
419419 "Tool '%s' not found. This tool was registered but is no longer available. "+
420420 "The MCP server may have been stopped. Try running '/mcp' to check server status.",
421- zotName))
421+ zutName))
422422 }
423423
424424 srv, ok := b.servers[mapping.serverName]
@@ -449,8 +449,8 @@ func (b *bridge) handleToolCall(zotName string, args json.RawMessage) ext.ToolRe
449449 err, mapping.serverName, mapping.mcpTool, mapping.serverName))
450450 }
451451
452- // Convert MCP result to zot ToolResult
453- return mcpResultToZot(result)
452+ // Convert MCP result to zut ToolResult
453+ return mcpResultToZut(result)
454454 }
455455
456456 // timeoutErrorMessage formats the user-facing text for a deadline error.
@@ -461,8 +461,8 @@ func timeoutErrorMessage(serverName string, requestTimeout int) string {
461461 requestTimeout, serverName)
462462 }
463463
464-// mcpResultToZot converts an MCP CallToolResult to a zot ToolResult.
465-func mcpResultToZot(result *mcp.CallToolResult) ext.ToolResult {
464+// mcpResultToZut converts an MCP CallToolResult to a zut ToolResult.
465+func mcpResultToZut(result *mcp.CallToolResult) ext.ToolResult {
466466 if result == nil {
467467 return ext.TextResult("(no result)")
468468 }
@@ -492,7 +492,7 @@ func mcpResultToZot(result *mcp.CallToolResult) ext.ToolResult {
492492 return tr
493493 }
494494
495-// startAll starts all configured servers without re-registering their tools with zot.
495+// startAll starts all configured servers without re-registering their tools with zut.
496496 func (b *bridge) startAll(ctx context.Context) error {
497497 var wg sync.WaitGroup
498498 errCh := make(chan error, len(b.servers))
@@ -523,7 +523,7 @@ func (b *bridge) startAll(ctx context.Context) error {
523523
524524 // startIdleReaper runs a background goroutine that kills idle servers.
525525 // It has no shutdown mechanism by design: it lives exactly as long as
526-// the extension process, and zot reaps the whole process on exit.
526+// the extension process, and zut reaps the whole process on exit.
527527 func (b *bridge) startIdleReaper() {
528528 go func() {
529529 ticker := time.NewTicker(30 * time.Second)
M examples/extensions/mcp-bridge/cache.go +1 -1
@@ -32,7 +32,7 @@ type toolCache struct {
3232 }
3333
3434 func toolCachePath() string {
35- return filepath.Join(zotHome(), "mcp-tools-cache.json")
35+ return filepath.Join(zutHome(), "mcp-tools-cache.json")
3636 }
3737
3838 func serverFingerprint(cfg ServerConfig) string {
M examples/extensions/mcp-bridge/config.go +11 -11
@@ -3,8 +3,8 @@
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
7-// 2. Project: .zot/mcp.json (in the current working directory)
6+// 1. Global: $ZUT_HOME/mcp.json
7+// 2. Project: .zut/mcp.json (in the current working directory)
88 //
99 // Project config overrides global config per-server (shallow merge).
1010 package main
@@ -42,23 +42,23 @@ type Config struct {
4242 MCPServers map[string]ServerConfig `json:"mcpServers"`
4343 }
4444
45-// zotHome returns the zot state directory.
46-func zotHome() string {
47- if h := os.Getenv("ZOT_HOME"); h != "" {
45+// zutHome returns the zut state directory.
46+func zutHome() string {
47+ if h := os.Getenv("ZUT_HOME"); h != "" {
4848 return h
4949 }
5050 if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {
51- return filepath.Join(xdg, "zot")
51+ return filepath.Join(xdg, "zut")
5252 }
5353 switch runtime.GOOS {
5454 case "darwin":
5555 home, _ := os.UserHomeDir()
56- return filepath.Join(home, "Library", "Application Support", "zot")
56+ return filepath.Join(home, "Library", "Application Support", "zut")
5757 case "windows":
58- return filepath.Join(os.Getenv("APPDATA"), "zot")
58+ return filepath.Join(os.Getenv("APPDATA"), "zut")
5959 default: // linux, freebsd, etc.
6060 home, _ := os.UserHomeDir()
61- return filepath.Join(home, ".local", "state", "zot")
61+ return filepath.Join(home, ".local", "state", "zut")
6262 }
6363 }
6464
@@ -68,14 +68,14 @@ func loadConfig(cwd string) (Config, error) {
6868 cfg := Config{MCPServers: make(map[string]ServerConfig)}
6969
7070 // 1. Global config
71- globalPath := filepath.Join(zotHome(), "mcp.json")
71+ globalPath := filepath.Join(zutHome(), "mcp.json")
7272 if err := mergeConfig(&cfg, globalPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
7373 return cfg, fmt.Errorf("global config %s: %w", globalPath, err)
7474 }
7575
7676 // 2. Project config (overrides global per-server)
7777 if cwd != "" {
78- projectPath := filepath.Join(cwd, ".zot", "mcp.json")
78+ projectPath := filepath.Join(cwd, ".zut", "mcp.json")
7979 if err := mergeConfig(&cfg, projectPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
8080 return cfg, fmt.Errorf("project config %s: %w", projectPath, err)
8181 }
M examples/extensions/mcp-bridge/config_test.go +4 -4
@@ -19,7 +19,7 @@ func writeJSON(t *testing.T, path, content string) {
1919 func TestLoadConfigProjectOverridesGlobal(t *testing.T) {
2020 home := t.TempDir()
2121 proj := t.TempDir()
22- t.Setenv("ZOT_HOME", home)
22+ t.Setenv("ZUT_HOME", home)
2323
2424 writeJSON(t, filepath.Join(home, "mcp.json"), `{
2525 "mcpServers": {
@@ -27,7 +27,7 @@ func TestLoadConfigProjectOverridesGlobal(t *testing.T) {
2727 "global-only": {"command": "g"}
2828 }
2929 }`)
30- writeJSON(t, filepath.Join(proj, ".zot", "mcp.json"), `{
30+ writeJSON(t, filepath.Join(proj, ".zut", "mcp.json"), `{
3131 "mcpServers": {
3232 "shared": {"command": "project-cmd"}
3333 }
@@ -50,7 +50,7 @@ func TestLoadConfigProjectOverridesGlobal(t *testing.T) {
5050 }
5151
5252 func TestLoadConfigMissingFilesIsNotAnError(t *testing.T) {
53- t.Setenv("ZOT_HOME", t.TempDir())
53+ t.Setenv("ZUT_HOME", t.TempDir())
5454 cfg, err := loadConfig(t.TempDir())
5555 if err != nil {
5656 t.Fatalf("loadConfig with no files: %v", err)
@@ -61,7 +61,7 @@ func TestLoadConfigMissingFilesIsNotAnError(t *testing.T) {
6161 }
6262
6363 func TestHandleSetupProjectRequiresCwd(t *testing.T) {
64- t.Setenv("ZOT_HOME", t.TempDir())
64+ t.Setenv("ZUT_HOME", t.TempDir())
6565 if _, err := handleSetup([]string{"add", "grep", "--project"}, ""); err == nil {
6666 t.Fatal("expected error for --project with unknown working directory")
6767 }
M examples/extensions/mcp-bridge/deferred_tools_test.go +2 -2
@@ -10,8 +10,8 @@ import (
1010 "testing"
1111 "time"
1212
13- "github.com/patriceckhart/zot/packages/agent/ext"
14- "github.com/patriceckhart/zot/packages/agent/extproto"
13+ "github.com/bnema/zut/packages/agent/ext"
14+ "github.com/bnema/zut/packages/agent/extproto"
1515 )
1616
1717 type bridgeExtensionHarness struct {
M examples/extensions/mcp-bridge/extension.json +1 -1
@@ -3,6 +3,6 @@
33 "version": "1.1.0",
44 "exec": "./mcp-bridge",
55 "language": "go",
6- "description": "Connect zot to MCP servers with on-demand tool discovery",
6+ "description": "Connect zut to MCP servers with on-demand tool discovery",
77 "enabled": true
88 }
M examples/extensions/mcp-bridge/fsutil_test.go +1 -1
@@ -80,7 +80,7 @@ func TestToolCacheVersionMismatchInvalidates(t *testing.T) {
8080
8181 func TestConfigFileWrittenOwnerOnly(t *testing.T) {
8282 tmp := t.TempDir()
83- t.Setenv("ZOT_HOME", tmp)
83+ t.Setenv("ZUT_HOME", tmp)
8484
8585 if _, err := handleSetup([]string{"add", "grep"}, tmp); err != nil {
8686 t.Fatalf("handleSetup: %v", err)
M examples/extensions/mcp-bridge/go.mod +4 -4
@@ -1,12 +1,12 @@
1-module github.com/patriceckhart/zot/examples/extensions/mcp-bridge
1+module github.com/bnema/zut/examples/extensions/mcp-bridge
22
3-go 1.25.5
3+go 1.26
44
5-replace github.com/patriceckhart/zot => ../../..
5+replace github.com/bnema/zut => ../../..
66
77 require (
8+ github.com/bnema/zut v0.1.0
89 github.com/mark3labs/mcp-go v0.55.1
9- github.com/patriceckhart/zot v0.2.47
1010 )
1111
1212 require (
M examples/extensions/mcp-bridge/main.go +10 -10
@@ -1,12 +1,12 @@
1-// mcp-bridge — Connect zot to MCP (Model Context Protocol) servers.
1+// mcp-bridge — Connect zut to MCP (Model Context Protocol) servers.
22 //
33 // This extension reads MCP server configurations from standard locations
44 // (same format as Claude Desktop, Cursor, etc.) and bridges their tools
5-// into zot so the LLM can call them.
5+// into zut so the LLM can call them.
66 //
77 // Config locations:
8-// - Global: $ZOT_HOME/mcp.json
9-// - Project: .zot/mcp.json
8+// - Global: $ZUT_HOME/mcp.json
9+// - Project: .zut/mcp.json
1010 //
1111 // Smart lazy: servers are spawned on startup to discover tools, then
1212 // killed after 5 minutes of idle time. On the next tool call, they're
@@ -30,7 +30,7 @@
3030 //
3131 // Install:
3232 //
33-// zot ext install .
33+// zut ext install .
3434 package main
3535
3636 import (
@@ -41,13 +41,13 @@ import (
4141 "strings"
4242 "time"
4343
44- "github.com/patriceckhart/zot/packages/agent/ext"
44+ "github.com/bnema/zut/packages/agent/ext"
4545 )
4646
4747 func main() {
4848 e := ext.New("mcp", "1.1.0")
4949
50- // Logger writes to stderr (captured by zot into ext logs)
50+ // Logger writes to stderr (captured by zut into ext logs)
5151 logger := log.New(os.Stderr, "[mcp-bridge] ", log.LstdFlags)
5252
5353 var b *bridge
@@ -105,7 +105,7 @@ func main() {
105105 }
106106
107107 func startBackgroundToolRefresh(e *ext.Extension, b *bridge, logger *log.Logger, cachePath string, cachedToolCount int) {
108- // Refresh discovery in the background so zot startup is not blocked. Only ask
108+ // Refresh discovery in the background so zut startup is not blocked. Only ask
109109 // for /reload-ext if the discovered tool cache actually changed; otherwise a
110110 // reload would just repeat this cycle without adding anything.
111111 go func() {
@@ -216,8 +216,8 @@ func mcpHelp(b *bridge) string {
216216 sb.WriteString(" /mcp setup templates List setup templates\n")
217217 sb.WriteString(" /mcp setup add <template> [options] Add a server template\n")
218218 sb.WriteString("\nSetup options\n")
219- sb.WriteString(" --global Write to $ZOT_HOME/mcp.json (default)\n")
220- sb.WriteString(" --project Write to .zot/mcp.json\n")
219+ sb.WriteString(" --global Write to $ZUT_HOME/mcp.json (default)\n")
220+ sb.WriteString(" --project Write to .zut/mcp.json\n")
221221 sb.WriteString(" --name <server-name> Use a custom configured server name\n")
222222 return strings.TrimRight(sb.String(), "\n")
223223 }
M examples/extensions/mcp-bridge/server.go +2 -2
@@ -157,7 +157,7 @@ func (s *managedServer) doStart(ctx context.Context) (*client.Client, []mcp.Tool
157157 Params: mcp.InitializeParams{
158158 ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
159159 ClientInfo: mcp.Implementation{
160- Name: "zot-mcp-bridge",
160+ Name: "zut-mcp-bridge",
161161 Version: "1.0.0",
162162 },
163163 Capabilities: mcp.ClientCapabilities{},
@@ -191,7 +191,7 @@ func (s *managedServer) startStdio() (*client.Client, error) {
191191 }
192192
193193 // Create stdio client (this spawns the subprocess). Run stdio MCP
194- // servers from the zot session's project directory, not the installed
194+ // servers from the zut session's project directory, not the installed
195195 // extension directory. Many MCP servers use their process cwd as their
196196 // project root when answering relative-path or directory-listing
197197 // requests, so inheriting mcp-bridge's cwd makes them operate on the
M examples/extensions/mcp-bridge/setup.go +4 -4
@@ -75,7 +75,7 @@ func setupHelp() string {
7575
7676 var b strings.Builder
7777 b.WriteString("mcp-bridge setup\n\n")
78- b.WriteString("Add a known MCP server template to your zot MCP config.\n\n")
78+ b.WriteString("Add a known MCP server template to your zut MCP config.\n\n")
7979 b.WriteString("Usage:\n")
8080 b.WriteString(" /mcp setup add <template> [--global|--project] [--name <server-name>]\n")
8181 b.WriteString(" /mcp setup templates\n\n")
@@ -88,7 +88,7 @@ func setupHelp() string {
8888 b.WriteString(" /mcp setup add grep\n")
8989 b.WriteString(" /mcp setup add filesystem --project\n")
9090 b.WriteString(" /mcp setup add context7 --global --name docs\n")
91- b.WriteString("\nDefault target is global: $ZOT_HOME/mcp.json. Run /reload-ext after changes.\n")
91+ b.WriteString("\nDefault target is global: $ZUT_HOME/mcp.json. Run /reload-ext after changes.\n")
9292 return b.String()
9393 }
9494
@@ -132,12 +132,12 @@ func handleSetup(args []string, cwd string) (string, error) {
132132 }
133133 }
134134
135- path := filepath.Join(zotHome(), "mcp.json")
135+ path := filepath.Join(zutHome(), "mcp.json")
136136 if target == "project" {
137137 if cwd == "" {
138138 return "", fmt.Errorf("--project requires a working directory, but none is known")
139139 }
140- path = filepath.Join(cwd, ".zot", "mcp.json")
140+ path = filepath.Join(cwd, ".zut", "mcp.json")
141141 }
142142
143143 cfg, err := readConfigFile(path)
M examples/extensions/mcp-bridge/setup_test.go +3 -3
@@ -9,7 +9,7 @@ import (
99
1010 func TestHandleSetupAddGrepGlobal(t *testing.T) {
1111 tmp := t.TempDir()
12- t.Setenv("ZOT_HOME", tmp)
12+ t.Setenv("ZUT_HOME", tmp)
1313
1414 out, err := handleSetup([]string{"add", "grep"}, tmp)
1515 if err != nil {
@@ -44,7 +44,7 @@ func TestHandleSetupAddFilesystemProject(t *testing.T) {
4444 t.Fatalf("handleSetup add filesystem: %v", err)
4545 }
4646
47- data, err := os.ReadFile(filepath.Join(cwd, ".zot", "mcp.json"))
47+ data, err := os.ReadFile(filepath.Join(cwd, ".zut", "mcp.json"))
4848 if err != nil {
4949 t.Fatalf("read project mcp.json: %v", err)
5050 }
@@ -60,7 +60,7 @@ func TestHandleSetupAddFilesystemProject(t *testing.T) {
6060
6161 func TestHandleSetupDuplicate(t *testing.T) {
6262 tmp := t.TempDir()
63- t.Setenv("ZOT_HOME", tmp)
63+ t.Setenv("ZUT_HOME", tmp)
6464
6565 if _, err := handleSetup([]string{"add", "grep"}, tmp); err != nil {
6666 t.Fatalf("first add failed: %v", err)
M examples/extensions/mcp-bridge/status_test.go +4 -4
@@ -198,8 +198,8 @@ func TestCompactErrClassifications(t *testing.T) {
198198 }
199199 }
200200
201-func TestMCPResultToZot(t *testing.T) {
202- if got := mcpResultToZot(nil); len(got.Content) == 0 || got.IsError {
201+func TestMCPResultToZut(t *testing.T) {
202+ if got := mcpResultToZut(nil); len(got.Content) == 0 || got.IsError {
203203 t.Fatalf("nil result: %+v", got)
204204 }
205205
@@ -207,7 +207,7 @@ func TestMCPResultToZot(t *testing.T) {
207207 Content: []mcp.Content{mcp.TextContent{Type: "text", Text: "hello"}},
208208 IsError: true,
209209 }
210- got := mcpResultToZot(res)
210+ got := mcpResultToZut(res)
211211 if !got.IsError {
212212 t.Fatal("IsError not propagated")
213213 }
@@ -215,7 +215,7 @@ func TestMCPResultToZot(t *testing.T) {
215215 t.Fatalf("content count = %d, want 1", len(got.Content))
216216 }
217217
218- empty := mcpResultToZot(&mcp.CallToolResult{})
218+ empty := mcpResultToZut(&mcp.CallToolResult{})
219219 if len(empty.Content) == 0 {
220220 t.Fatal("empty MCP result must produce placeholder content")
221221 }
M examples/extensions/scratchpad/README.md +6 -6
@@ -1,4 +1,4 @@
1-# scratchpad — example zot extension (TypeScript, source-run)
1+# scratchpad — example zut extension (TypeScript, source-run)
22
33 Real `.ts` (not `.js`), no build step, no SDK. Runs via `npx -y tsx
44 ./index.ts`, which downloads `tsx` into npm's cache on first
@@ -20,14 +20,14 @@ Node 18+ and `npx` (bundled with npm).
2020 From this directory:
2121
2222 ```bash
23-zot ext install .
23+zut ext install .
2424 ```
2525
26-This copies the manifest + source into `$ZOT_HOME/extensions/scratchpad/`.
26+This copies the manifest + source into `$ZUT_HOME/extensions/scratchpad/`.
2727
2828 ## Use
2929
30-In zot:
30+In zut:
3131
3232 - `/note remind me to update the changelog` — appends to the scratchpad
3333 - `/notes` — shows everything stored
@@ -41,8 +41,8 @@ The model also has a `read_notes` tool. Ask it:
4141
4242 ## Storage
4343
44-Notes persist as JSONL at `<cwd>/.zot/scratchpad-notes.jsonl`. The
45-file is created on first `/note` and survives zot restarts. Each line
44+Notes persist as JSONL at `<cwd>/.zut/scratchpad-notes.jsonl`. The
45+file is created on first `/note` and survives zut restarts. Each line
4646 is one note: `{"at":"2026-04-19T13:00:00.000Z","text":"..."}`.
4747
4848 Scope is per-project: switching to a different cwd gives you a
M examples/extensions/scratchpad/index.ts +17 -10
@@ -1,4 +1,4 @@
1-// scratchpad — a real .ts zot extension with no SDK and no build step.
1+// scratchpad — a real .ts zut extension with no SDK and no build step.
22 //
33 // Runs via `npx --yes tsx index.ts` (declared in extension.json).
44 // First invocation downloads tsx into npm's cache; every subsequent
@@ -47,7 +47,7 @@ interface ToolCall {
4747 interface HelloAck {
4848 type: "hello_ack";
4949 protocol_version: number;
50- zot_version: string;
50+ zut_version: string;
5151 provider: string;
5252 model: string;
5353 cwd: string;
@@ -89,23 +89,23 @@ function send(frame: Frame): void {
8989 }
9090
9191 function log(msg: string): void {
92- // stderr is captured by zot to $ZOT_HOME/logs/ext-<name>.log;
92+ // stderr is captured by zut to $ZUT_HOME/logs/ext-<name>.log;
9393 // safe for debug output. stdout is reserved for the protocol.
9494 stderr.write(`[${NAME}] ${msg}\n`);
9595 }
9696
9797 // ---- the scratchpad state itself ----
9898 //
99-// Notes persist as JSONL under <cwd>/.zot/scratchpad-notes.jsonl so
100-// they survive zot restarts and stay scoped to the project. The path
99+// Notes persist as JSONL under <cwd>/.zut/scratchpad-notes.jsonl so
100+// they survive zut restarts and stay scoped to the project. The path
101101 // is resolved once HelloAck arrives (which carries cwd); until then
102102 // notesPath is empty and reads/writes no-op safely.
103103 //
104104 // One note per line, format: {"at":"<iso>","text":"<body>"}
105105 // Append-only on /note; full rewrite on /clear-notes.
106106 //
107-// Single-writer assumption: only one zot session per cwd at a time.
108-// Concurrent writes from two zot instances would interleave but not
107+// Single-writer assumption: only one zut session per cwd at a time.
108+// Concurrent writes from two zut instances would interleave but not
109109 // corrupt JSONL line boundaries on POSIX (writes ≤ PIPE_BUF are
110110 // atomic). Good enough for a demo.
111111
@@ -115,7 +115,7 @@ let notes: Note[] = [];
115115 let notesPath = "";
116116
117117 function setNotesPath(cwd: string): void {
118- notesPath = join(cwd, ".zot", "scratchpad-notes.jsonl");
118+ notesPath = join(cwd, ".zut", "scratchpad-notes.jsonl");
119119 loadNotes();
120120 }
121121
@@ -177,6 +177,7 @@ function renderNotes(): string {
177177
178178 send({
179179 type: "hello",
180+ protocol_version: 2,
180181 name: NAME,
181182 version: VERSION,
182183 capabilities: ["commands", "tools"],
@@ -209,7 +210,7 @@ send({
209210 },
210211 });
211212
212-// Sentinel: tells zot all initial registrations are flushed so the
213+// Sentinel: tells zut all initial registrations are flushed so the
213214 // agent's tool registry can be built without racing the read loop.
214215 send({ type: "ready" });
215216
@@ -251,8 +252,14 @@ rl.on("close", () => {
251252 });
252253
253254 function handleHelloAck(ack: HelloAck): void {
255+ if (ack.protocol_version !== 2) {
256+ log(`unsupported protocol version: ${ack.protocol_version}`);
257+ process.exitCode = 1;
258+ rl.close();
259+ return;
260+ }
254261 log(
255- `connected to zot ${ack.zot_version} ` +
262+ `connected to zut ${ack.zut_version} ` +
256263 `(${ack.provider}/${ack.model}, cwd=${ack.cwd})`,
257264 );
258265 if (ack.cwd) setNotesPath(ack.cwd);
M examples/extensions/secret/README.md +3 -3
@@ -1,4 +1,4 @@
1-# secret — example zot extension
1+# secret — example zut extension
22
33 Demonstrates **secret collection via a masked panel**: the model asks
44 for a resource that needs a credential, the extension collects the
@@ -50,12 +50,12 @@ go build -o secret .
5050 ## Install
5151
5252 ```bash
53-zot ext install .
53+zut ext install .
5454 ```
5555
5656 ## Try it
5757
58-In zot, ask:
58+In zut, ask:
5959
6060 > Fetch https://internal.example.com/report — it needs a password.
6161
M examples/extensions/secret/main.go +3 -3
@@ -20,9 +20,9 @@
2020 //
2121 // Install:
2222 //
23-// zot ext install .
23+// zut ext install .
2424 //
25-// Try it — ask zot something like:
25+// Try it — ask zut something like:
2626 //
2727 // "Fetch https://internal.example.com/report — it needs a password."
2828 //
@@ -38,7 +38,7 @@ import (
3838 "strings"
3939 "sync"
4040
41- "github.com/patriceckhart/zot/packages/agent/ext"
41+ "github.com/bnema/zut/packages/agent/ext"
4242 )
4343
4444 const schema = `{
M examples/extensions/tasked-phases/README.md +9 -9
@@ -1,6 +1,6 @@
11 # tasked-phases
22
3-A zot extension for keeping a spec, phased plan, and checklist in one place.
3+A zut extension for keeping a spec, phased plan, and checklist in one place.
44 It provides:
55
66 - the `tasked_phases` tool for persistent spec/phase/task state;
@@ -24,7 +24,7 @@ Successful mutations return one progress line; use `get_status` or
2424
2525 ## Build and run
2626
27-From the zot repository:
27+From the zut repository:
2828
2929 ```bash
3030 cd examples/extensions/tasked-phases
@@ -32,31 +32,31 @@ go build -o tasked-phases .
3232
3333 # One-run development load, launched from the project you want to track:
3434 cd /path/to/project
35-zot --no-ext --ext /path/to/zot/examples/extensions/tasked-phases
35+zut --no-ext --ext /path/to/zut/examples/extensions/tasked-phases
3636
3737 # Or install it globally (run this from the extension directory):
38-cd /path/to/zot/examples/extensions/tasked-phases
39-zot ext install --build=go .
38+cd /path/to/zut/examples/extensions/tasked-phases
39+zut ext install --build=go .
4040 ```
4141
4242 `--build=go` explicitly builds the manifest-declared executable in the staged
4343 installation. If an incomplete copy was installed already, remove it with
44-`zot ext remove tasked-phases --yes` before reinstalling. You can also build
45-manually with `go build -o tasked-phases .` and then use `zot ext install .`.
44+`zut ext remove tasked-phases --yes` before reinstalling. You can also build
45+manually with `go build -o tasked-phases .` and then use `zut ext install .`.
4646
4747 Then use `/phases` or ask the model to create a spec and phased checklist with
4848 `tasked_phases`. The extension keys its durable state by the host-reported
4949 project CWD.
5050
5151 The extension bundles its workflow skill under `skills/tasked-phases/`, so
52-`zot ext install` makes the guidance available automatically. The standalone
52+`zut ext install` makes the guidance available automatically. The standalone
5353 copy at `examples/skills/tasked-phases/SKILL.md` remains useful when you want
5454 the same workflow without installing the extension.
5555
5656 ## Persistence and turn focus
5757
5858 The extension stores one restrictive state file per project CWD as a fallback
59-for hosts that do not persist sessions. When zot session persistence is
59+for hosts that do not persist sessions. When zut session persistence is
6060 available, the plan is also stored as extension-owned state on each session
6161 branch and restored when sessions are opened, switched, or forked.
6262
M examples/extensions/tasked-phases/main.go +1 -1
@@ -6,7 +6,7 @@ import (
66 "os"
77 "sync"
88
9- "github.com/patriceckhart/zot/packages/agent/ext"
9+ "github.com/bnema/zut/packages/agent/ext"
1010 )
1111
1212 const (
M examples/extensions/tasked-phases/main_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "strings"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/agent/ext"
7+ "github.com/bnema/zut/packages/agent/ext"
88 )
99
1010 func TestBuildChromeHidesEmptyPlan(t *testing.T) {
M examples/extensions/tasked-phases/state.go +1 -1
@@ -1043,7 +1043,7 @@ func (s *stateStore) load(dataDir, cwd string) error {
10431043 }
10441044
10451045 // activateSession replaces the project fallback with the opaque snapshot
1046-// supplied by zot for the active session branch. An empty snapshot means the
1046+// supplied by zut for the active session branch. An empty snapshot means the
10471047 // branch has no extension state yet and therefore starts clean.
10481048 func (s *stateStore) activateSession(raw json.RawMessage) error {
10491049 s.mu.Lock()
M examples/extensions/todo/README.md +4 -4
@@ -1,8 +1,8 @@
1-# todo — example zot extension (Go, interactive panel)
1+# todo — example zut extension (Go, interactive panel)
22
33 Demonstrates the interactive panel API plus a companion tool the model
44 can call. The slash command opens a persistent todo panel; the tool lets
5-zot read and update the same todo list.
5+zut read and update the same todo list.
66
77 ## Requirements
88
@@ -13,7 +13,7 @@ Go 1.22+.
1313 From this directory:
1414
1515 ```bash
16-zot ext install .
16+zut ext install .
1717 ```
1818
1919 The example is configured to run directly from source:
@@ -45,7 +45,7 @@ If you do build it, change `extension.json` back to:
4545
4646 ## Use
4747
48-In zot:
48+In zut:
4949
5050 - `/todo` opens the panel
5151
M examples/extensions/todo/go.mod +4 -4
@@ -1,7 +1,7 @@
1-module zot-todo-extension
1+module zut-todo-extension
22
3-go 1.22
3+go 1.26
44
5-require github.com/patriceckhart/zot v0.0.0
5+require github.com/bnema/zut v0.1.0
66
7-replace github.com/patriceckhart/zot => /Users/pat/Developer/zot
7+replace github.com/bnema/zut => ../../..
M examples/extensions/todo/main.go +1 -1
@@ -8,7 +8,7 @@ import (
88 "strings"
99 "sync"
1010
11- "github.com/patriceckhart/zot/packages/agent/ext"
11+ "github.com/bnema/zut/packages/agent/ext"
1212 )
1313
1414 type todoItem struct {
D examples/extensions/todo/zot-todo-extension +0 -0 binary file: no text diff

binary file: no text diff

M examples/extensions/weather/README.md +5 -5
@@ -1,4 +1,4 @@
1-# weather — example zot extension (Go, tool-providing)
1+# weather — example zut extension (Go, tool-providing)
22
33 Demonstrates the **tool registration** half of the extension protocol
44 (phase 2). The extension exposes a `weather(city)` tool that the LLM
@@ -15,16 +15,16 @@ go build -o weather .
1515 ## Install
1616
1717 ```bash
18-zot ext install .
18+zut ext install .
1919 ```
2020
2121 Copies the directory (manifest + binary) into
22-`$ZOT_HOME/extensions/weather/`. zot picks it up the next time you
22+`$ZUT_HOME/extensions/weather/`. zut picks it up the next time you
2323 launch the TUI and registers the tool with the agent.
2424
2525 ## Use
2626
27-In zot, ask:
27+In zut, ask:
2828
2929 - "What's the weather in Berlin?"
3030 - "Compare the weather in Tokyo and Reykjavik."
@@ -35,7 +35,7 @@ anything by hand.
3535
3636 ## Add the leading slash to your /help table
3737
38-The tool also shows up in the system prompt's tool list because zot
38+The tool also shows up in the system prompt's tool list because zut
3939 folds extension tools into the agent's registry at startup.
4040
4141 ## See also
M examples/extensions/weather/main.go +3 -3
@@ -10,8 +10,8 @@
1010 // go build -o weather .
1111 //
1212 // Then drop it next to its extension.json under
13-// $ZOT_HOME/extensions/weather/, or run `zot ext install ./weather`
14-// from this directory. After that, asking zot anything like
13+// $ZUT_HOME/extensions/weather/, or run `zut ext install ./weather`
14+// from this directory. After that, asking zut anything like
1515 // "what's the weather in Berlin?" makes the model call the tool.
1616 package main
1717
@@ -21,7 +21,7 @@ import (
2121 "fmt"
2222 "strings"
2323
24- "github.com/patriceckhart/zot/packages/agent/ext"
24+ "github.com/bnema/zut/packages/agent/ext"
2525 )
2626
2727 const weatherSchema = `{
M examples/rpc/go/main.go +4 -4
@@ -1,6 +1,6 @@
1-// Tiny Go program that drives `zot rpc` as a subprocess.
1+// Tiny Go program that drives `zut rpc` as a subprocess.
22 //
3-// For an in-process Go embedding, use github.com/patriceckhart/zot/packages/agent/sdk
3+// For an in-process Go embedding, use github.com/bnema/zut/packages/agent/sdk
44 // instead — this example exists to show what consumers in OTHER
55 // languages have to do.
66 package main
@@ -22,7 +22,7 @@ func main() {
2222 }
2323 prompt := strings.Join(os.Args[1:], " ")
2424
25- cmd := exec.Command("zot", "rpc")
25+ cmd := exec.Command("zut", "rpc")
2626 cmd.Stderr = os.Stderr
2727 stdin, err := cmd.StdinPipe()
2828 must(err)
@@ -30,7 +30,7 @@ func main() {
3030 must(err)
3131 must(cmd.Start())
3232
33- if tok := os.Getenv("ZOTCORE_RPC_TOKEN"); tok != "" {
33+ if tok := os.Getenv("ZUTCORE_RPC_TOKEN"); tok != "" {
3434 send(stdin, map[string]any{"id": "0", "type": "hello", "token": tok})
3535 }
3636 send(stdin, map[string]any{
R examples/rpc/node/zot-client.js → examples/rpc/node/zut-client.js +8 -8 renamed, 85% similar
@@ -1,9 +1,9 @@
1-// Minimal Node client for the `zot rpc` JSON protocol.
1+// Minimal Node client for the `zut rpc` JSON protocol.
22 //
33 // Usage:
4-// node zot-client.js "fix the failing test"
4+// node zut-client.js "fix the failing test"
55 //
6-// Spawns `zot rpc`, sends one prompt, prints assistant text as it
6+// Spawns `zut rpc`, sends one prompt, prints assistant text as it
77 // streams, and exits when the turn finishes. Pure stdlib — no
88 // dependencies. See docs/rpc.md for the full schema.
99 "use strict";
@@ -12,9 +12,9 @@ const { spawn } = require("node:child_process");
1212 const readline = require("node:readline");
1313 const { randomBytes } = require("node:crypto");
1414
15-class ZotClient {
15+class ZutClient {
1616 constructor(...flags) {
17- this.proc = spawn("zot", ["rpc", ...flags], {
17+ this.proc = spawn("zut", ["rpc", ...flags], {
1818 stdio: ["pipe", "pipe", "inherit"],
1919 env: process.env,
2020 });
@@ -52,13 +52,13 @@ class ZotClient {
5252 async function main() {
5353 const prompt = process.argv.slice(2).join(" ");
5454 if (!prompt) {
55- console.error("usage: node zot-client.js <prompt>");
55+ console.error("usage: node zut-client.js <prompt>");
5656 process.exit(2);
5757 }
5858
59- const client = new ZotClient();
59+ const client = new ZutClient();
6060
61- const token = process.env.ZOTCORE_RPC_TOKEN;
61+ const token = process.env.ZUTCORE_RPC_TOKEN;
6262 if (token) client.send({ type: "hello", token });
6363
6464 client.send({ type: "prompt", message: prompt });
D examples/rpc/python/__pycache__/zot_client.cpython-314.pyc +0 -0 binary file: no text diff

binary file: no text diff

R examples/rpc/python/zot_client.py → examples/rpc/python/zut_client.py +8 -8 renamed, 89% similar
@@ -1,9 +1,9 @@
1-"""Minimal Python client for the `zot rpc` JSON protocol.
1+"""Minimal Python client for the `zut rpc` JSON protocol.
22
33 Usage:
4- python zot_client.py "fix the failing test"
4+ python zut_client.py "fix the failing test"
55
6-Spawns `zot rpc`, sends one prompt, prints assistant text as it streams,
6+Spawns `zut rpc`, sends one prompt, prints assistant text as it streams,
77 and exits when the turn finishes. No external dependencies — stdlib
88 only. Implements just enough of the protocol to be useful as a
99 starting point; see docs/rpc.md for the full schema.
@@ -18,9 +18,9 @@ import threading
1818 import uuid
1919
2020
21-class ZotClient:
21+class ZutClient:
2222 def __init__(self, *flags: str) -> None:
23- argv = ["zot", "rpc", *flags]
23+ argv = ["zut", "rpc", *flags]
2424 env = os.environ.copy()
2525 self.proc = subprocess.Popen(
2626 argv,
@@ -67,13 +67,13 @@ class ZotClient:
6767
6868 def main() -> int:
6969 if len(sys.argv) < 2:
70- print("usage: zot_client.py <prompt>", file=sys.stderr)
70+ print("usage: zut_client.py <prompt>", file=sys.stderr)
7171 return 2
7272 prompt = " ".join(sys.argv[1:])
7373
74- client = ZotClient()
74+ client = ZutClient()
7575 try:
76- token = os.environ.get("ZOTCORE_RPC_TOKEN")
76+ token = os.environ.get("ZUTCORE_RPC_TOKEN")
7777 if token:
7878 client.send(type="hello", token=token)
7979
M examples/rpc/shell/prompt.sh +6 -6
@@ -1,5 +1,5 @@
11 #!/usr/bin/env bash
2-# Minimal shell client for the `zot rpc` JSON protocol.
2+# Minimal shell client for the `zut rpc` JSON protocol.
33 #
44 # Usage:
55 # ./prompt.sh "fix the failing test"
@@ -18,17 +18,17 @@ prompt="$*"
1818 # Build the prompt command frame with jq so quotes are escaped properly.
1919 cmd=$(jq -nc --arg msg "$prompt" '{id:"1",type:"prompt",message:$msg}')
2020
21-# Pipe the command into zot rpc; pipe its stdout through jq to react
21+# Pipe the command into zut rpc; pipe its stdout through jq to react
2222 # to events. The trailing `cat` keeps the input pipe open until the
2323 # subprocess exits on its own (after `done`).
2424 {
25- if [ -n "${ZOTCORE_RPC_TOKEN:-}" ]; then
26- jq -nc --arg t "$ZOTCORE_RPC_TOKEN" '{id:"0",type:"hello",token:$t}'
25+ if [ -n "${ZUTCORE_RPC_TOKEN:-}" ]; then
26+ jq -nc --arg t "$ZUTCORE_RPC_TOKEN" '{id:"0",type:"hello",token:$t}'
2727 fi
2828 echo "$cmd"
29- # Block here so stdin stays open until zot exits.
29+ # Block here so stdin stays open until zut exits.
3030 cat
31-} | zot rpc | while IFS= read -r line; do
31+} | zut rpc | while IFS= read -r line; do
3232 type=$(echo "$line" | jq -r '.type // empty')
3333 case "$type" in
3434 text_delta)
M examples/sdk/main.go +3 -3
@@ -1,5 +1,5 @@
1-// In-process Go embedding of the zot agent runtime via the sdk package.
2-// Compare to examples/rpc/go which spawns `zot rpc` as a subprocess.
1+// In-process Go embedding of the zut agent runtime via the sdk package.
2+// Compare to examples/rpc/go which spawns `zut rpc` as a subprocess.
33 package main
44
55 import (
@@ -8,7 +8,7 @@ import (
88 "os"
99 "strings"
1010
11- "github.com/patriceckhart/zot/packages/agent/sdk"
11+ "github.com/bnema/zut/packages/agent/sdk"
1212 )
1313
1414 func main() {
M examples/themes/custom-spinner.json +1 -1
@@ -1,6 +1,6 @@
11 {
22 "name": "custom-spinner",
3- "description": "An alternative spinner for zot that only displays a single spinner text.",
3+ "description": "An alternative spinner for zut that only displays a single spinner text.",
44 "colors": {
55 "dark": {
66 "spinner_frames": [
M examples/themes/vercel.json +2 -2
@@ -1,6 +1,6 @@
11 {
22 "name": "vercel",
3- "description": "Vercel-inspired zot theme based on gantoreno/vscode-vercel. Uses the same core palette as the VS Code theme: pink keywords, blue constants/links, purple functions, green strings/success, amber warnings, and neutral Vercel grays for chrome.",
3+ "description": "Vercel-inspired zut theme based on gantoreno/vscode-vercel. Uses the same core palette as the VS Code theme: pink keywords, blue constants/links, purple functions, green strings/success, amber warnings, and neutral Vercel grays for chrome.",
44 "color_descriptions": {
55 "fg": "Default foreground text, based on Vercel editor.foreground / terminal.foreground.",
66 "muted": "Secondary text, dividers, gutters, footers, inactive hints, based on Vercel foreground/sidebar/status gray.",
@@ -8,7 +8,7 @@
88 "user": "User role label color; kept for compatibility with older UI surfaces.",
99 "user_bubble_bg": "Background behind user message rows, based on Vercel editor.selectionBackground / lineHighlightBackground.",
1010 "user_bubble_fg": "Text foreground inside user message rows.",
11- "assistant": "Assistant/zot accent color and welcome label, mapped to Vercel blue.",
11+ "assistant": "Assistant/zut accent color and welcome label, mapped to Vercel blue.",
1212 "tool": "Tool names, successful marks, additions in diffs, based on Vercel green.",
1313 "tool_out": "Plain tool output text.",
1414 "error": "Errors, refused tool calls, deletions in diffs, based on Vercel error/list error colors.",
M go.mod +1 -1
@@ -1,4 +1,4 @@
1-module github.com/patriceckhart/zot
1+module github.com/bnema/zut
22
33 go 1.26
44
M install.ps1 +15 -15
@@ -1,16 +1,16 @@
1-# zot installer for Windows (PowerShell).
1+# zut installer for Windows (PowerShell).
22 #
33 # Usage (in PowerShell):
4-# iwr -useb https://raw.githubusercontent.com/patriceckhart/zot/main/install.ps1 | iex
4+# iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex
55 #
66 # Or with arguments:
7-# $env:ZOT_VERSION = "v0.0.1"
8-# $env:ZOT_PREFIX = "$HOME\bin"
9-# iwr -useb https://raw.githubusercontent.com/patriceckhart/zot/main/install.ps1 | iex
7+# $env:ZUT_VERSION = "v0.1.0"
8+# $env:ZUT_PREFIX = "$HOME\bin"
9+# iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex
1010 #
1111 # Detects architecture, downloads the matching .zip from the GitHub
12-# release, verifies the sha256 against checksums.txt, extracts zot.exe,
13-# and moves it into $ZOT_PREFIX (defaults to $HOME\bin, added to PATH
12+# release, verifies the sha256 against checksums.txt, extracts zut.exe,
13+# and moves it into $ZUT_PREFIX (defaults to $HOME\bin, added to PATH
1414 # via the User environment if missing).
1515 #
1616 # $env:GITHUB_TOKEN is optional for the public repo. Set it to a PAT
@@ -21,15 +21,15 @@
2121
2222 [CmdletBinding()]
2323 param(
24- [string]$Version = $env:ZOT_VERSION,
25- [string]$Prefix = $env:ZOT_PREFIX
24+ [string]$Version = $env:ZUT_VERSION,
25+ [string]$Prefix = $env:ZUT_PREFIX
2626 )
2727
2828 $ErrorActionPreference = "Stop"
2929
30-$owner = "patriceckhart"
31-$repo = "zot"
32-$binary = "zot"
30+$owner = "bnema"
31+$repo = "zut"
32+$binary = "zut"
3333
3434 # Build Authorization header list once; used on every HTTP call so the
3535 # script works against private repos when $env:GITHUB_TOKEN is set.
@@ -73,7 +73,7 @@ if ($Version -eq "latest") {
7373 # GitHub's API wants a User-Agent; Invoke-RestMethod sets one, but be
7474 # explicit so corporate proxies that strip it don't trip a 403.
7575 $apiHeaders = @{} + $headers
76- if (-not $apiHeaders.ContainsKey("User-Agent")) { $apiHeaders["User-Agent"] = "zot-installer" }
76+ if (-not $apiHeaders.ContainsKey("User-Agent")) { $apiHeaders["User-Agent"] = "zut-installer" }
7777 $apiHeaders["Accept"] = "application/vnd.github+json"
7878
7979 try {
@@ -106,7 +106,7 @@ $baseUrl = "https://github.com/$owner/$repo/releases/download/$Version"
106106 $archiveUrl = "$baseUrl/$archive"
107107 $checksumUrl = "$baseUrl/checksums.txt"
108108
109-$tmp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("zot-install-" + [System.Guid]::NewGuid().ToString("N").Substring(0,8)))
109+$tmp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("zut-install-" + [System.Guid]::NewGuid().ToString("N").Substring(0,8)))
110110
111111 try {
112112 Msg "downloading $archive"
@@ -152,7 +152,7 @@ try {
152152 Warn " `$env:Path = `"$Prefix;`$env:Path`""
153153 }
154154
155- Msg "installed. run: zot --help"
155+ Msg "installed. run: zut --help"
156156 }
157157 finally {
158158 Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
M install.sh +15 -15
@@ -1,24 +1,24 @@
11 #!/usr/bin/env bash
22 #
3-# zot installer.
3+# zut installer.
44 #
55 # Usage:
6-# curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash
7-# curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash -s -- v0.0.1 ~/bin
6+# curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash
7+# curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash -s -- v0.1.0 ~/bin
88 #
99 # Positional arguments:
10-# $1 version — release tag (e.g. v0.0.1). Defaults to "latest".
10+# $1 version — release tag (e.g. v0.1.0). Defaults to "latest".
1111 # $2 prefix — install directory. Defaults to the first writable
1212 # directory in: /usr/local/bin, $HOME/.local/bin,
1313 # $HOME/bin. Created if missing. Add it to your PATH
1414 # if it isn't already.
1515 #
1616 # Environment overrides:
17-# ZOT_VERSION same as $1
18-# ZOT_PREFIX same as $2
17+# ZUT_VERSION same as $1
18+# ZUT_PREFIX same as $2
1919 # GITHUB_TOKEN personal access token — required while the repo is
2020 # private, ignored once it goes public. Must have at
21-# least `contents:read` scope on the zot repository.
21+# least `contents:read` scope on the zut repository.
2222 #
2323 # The script detects your OS and architecture, downloads the matching
2424 # archive from the GitHub release, verifies the sha256 against the
@@ -28,12 +28,12 @@
2828
2929 set -euo pipefail
3030
31-OWNER="patriceckhart"
32-REPO="zot"
33-BINARY="zot"
31+OWNER="bnema"
32+REPO="zut"
33+BINARY="zut"
3434
35-VERSION="${1:-${ZOT_VERSION:-latest}}"
36-PREFIX="${2:-${ZOT_PREFIX:-}}"
35+VERSION="${1:-${ZUT_VERSION:-latest}}"
36+PREFIX="${2:-${ZUT_PREFIX:-}}"
3737
3838 msg() { printf "\033[1m==>\033[0m %s\n" "$*"; }
3939 warn() { printf "\033[33mwarn:\033[0m %s\n" "$*" >&2; }
@@ -175,6 +175,6 @@ case ":$PATH:" in
175175 ;;
176176 esac
177177
178-msg "installed $("$PREFIX/$BINARY" --version 2>/dev/null || echo zot)"
179-msg "run: zot (interactive tui)"
180-msg "run: zot --help (all flags and subcommands)"
178+msg "installed $("$PREFIX/$BINARY" --version 2>/dev/null || echo zut)"
179+msg "run: zut (interactive tui)"
180+msg "run: zut --help (all flags and subcommands)"
M packages/agent/args.go +34 -34
@@ -6,8 +6,8 @@ import (
66 "strconv"
77 "strings"
88
9- "github.com/patriceckhart/zot/packages/agent/tools"
10- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/agent/tools"
10+ "github.com/bnema/zut/packages/tui"
1111 "golang.org/x/term"
1212 )
1313
@@ -81,12 +81,12 @@ type Args struct {
8181 // NoSkill disables ALL skill discovery for this run, including
8282 // the built-in skills compiled into the binary. The system
8383 // prompt loses its "Available skills" manifest and the `skill`
84- // tool isn't registered. Useful for running zot without any
84+ // tool isn't registered. Useful for running zut without any
8585 // extra context biasing the model.
8686 NoSkill bool
8787
8888 // WithSkills controls loading user-installed skills from
89- // $ZOT_HOME/skills/, .zot/skills/, .claude/skills/, and
89+ // $ZUT_HOME/skills/, .zut/skills/, .claude/skills/, and
9090 // .agents/skills/. It defaults to true; --no-skill disables all
9191 // skill discovery, including built-ins.
9292 WithSkills bool
@@ -107,8 +107,8 @@ type Args struct {
107107 // freely so automated workflows keep working.
108108 NoYolo bool
109109
110- // Yes accepts zotfile launch consent without an interactive
111- // Allow? prompt (zot run -y / --yes). Durable consent receipts
110+ // Yes accepts zutfile launch consent without an interactive
111+ // Allow? prompt (zut run -y / --yes). Durable consent receipts
112112 // are still written for modes other than bash ask.
113113 Yes bool
114114
@@ -119,7 +119,7 @@ type Args struct {
119119
120120 Prompt string // concatenated positional args
121121
122- // StartupPre is an optional zotfile entry.pre value. Interactive
122+ // StartupPre is an optional zutfile entry.pre value. Interactive
123123 // mode auto-submits it once at startup before InitialInput handling.
124124 StartupPre string
125125
@@ -137,8 +137,8 @@ type Args struct {
137137 // locally.
138138 Subagent string
139139
140- // AgentName/AgentDataDir/PermissionSet are populated by `zot run`
141- // for local zotfile agents. They scope sessions and enforce the
140+ // AgentName/AgentDataDir/PermissionSet are populated by `zut run`
141+ // for local zutfile agents. They scope sessions and enforce the
142142 // manifest's declared file/bash permissions.
143143 AgentName string
144144 AgentDataDir string
@@ -364,7 +364,7 @@ func ParseArgs(in []string) (Args, error) {
364364 }
365365
366366 // PrintHelp writes the help text to stderr. When stderr is a TTY it
367-// uses the same palette as zot's TUI; when redirected it falls back to
367+// uses the same palette as zut's TUI; when redirected it falls back to
368368 // plain text with no ANSI escapes.
369369 func PrintHelp(version string) {
370370 th := tui.Dark
@@ -427,42 +427,42 @@ func PrintHelp(version string) {
427427 fmt.Fprintln(os.Stderr)
428428 var headline string
429429 if useColor {
430- headline = th.AccentBar(th.Assistant) + assistant(tui.Bold("zot. yet another coding agent harness."))
430+ headline = th.AccentBar(th.Assistant) + assistant(tui.Bold("zut. yet another coding agent harness."))
431431 } else {
432- headline = "zot. yet another coding agent harness."
432+ headline = "zut. yet another coding agent harness."
433433 }
434434 fmt.Fprintln(os.Stderr, headline)
435435 fmt.Fprintln(os.Stderr, muted("ask anything, or type /help inside the tui to see commands."))
436436 fmt.Fprintf(os.Stderr, "%s %s\n", muted("version:"), fg(version))
437437
438438 section("modes",
439- row{"zot", "interactive tui"},
440- row{"zot \"prompt\"", "interactive, pre-filled prompt"},
441- row{"zot -p \"prompt\"", "print final text, exit"},
442- row{"zot --stream \"prompt\"", "stream assistant text live, exit"},
443- row{"zot --json \"prompt\"", "newline-delimited json events, exit"},
444- row{"zot rpc", "json-rpc loop on stdin/stdout (see docs/rpc.md)"},
439+ row{"zut", "interactive tui"},
440+ row{"zut \"prompt\"", "interactive, pre-filled prompt"},
441+ row{"zut -p \"prompt\"", "print final text, exit"},
442+ row{"zut --stream \"prompt\"", "stream assistant text live, exit"},
443+ row{"zut --json \"prompt\"", "newline-delimited json events, exit"},
444+ row{"zut rpc", "json-rpc loop on stdin/stdout (see docs/rpc.md)"},
445445 )
446446 section("extensions",
447- row{"zot ext list", "list installed extensions"},
448- row{"zot ext install <path|git-url>", "install from a local path or Git URL"},
449- row{"zot ext install --build=go <path>", "build local Go source, then install"},
450- row{"zot --ext ./path/to/ext", "load an extension for this run only"},
451- row{"zot ext help", "show all extension subcommands"},
447+ row{"zut ext list", "list installed extensions"},
448+ row{"zut ext install <path|git-url>", "install from a local path or Git URL"},
449+ row{"zut ext install --build=go <path>", "build local Go source, then install"},
450+ row{"zut --ext ./path/to/ext", "load an extension for this run only"},
451+ row{"zut ext help", "show all extension subcommands"},
452452 )
453453 section("self-update",
454- row{"zot update", "download and install the latest release"},
455- row{"zot update --check", "show whether a new release is available"},
454+ row{"zut update", "download and install the latest release"},
455+ row{"zut update --check", "show whether a new release is available"},
456456 )
457457 section("telegram",
458- row{"zot telegram-bot setup", "configure a telegram bot (from BotFather)"},
459- row{"zot telegram-bot run", "foreground bridge (ctrl+c to stop)"},
460- row{"zot telegram-bot start", "background bridge (detached)"},
461- row{"zot telegram-bot stop", "stop the background bridge"},
462- row{"zot telegram-bot logs [-f]", "tail the background bridge log"},
463- row{"zot telegram-bot status", "config + running state"},
464- row{"zot telegram-bot reset", "forget saved token"},
465- row{"zot tg ...", "short alias for telegram-bot"},
458+ row{"zut telegram-bot setup", "configure a telegram bot (from BotFather)"},
459+ row{"zut telegram-bot run", "foreground bridge (ctrl+c to stop)"},
460+ row{"zut telegram-bot start", "background bridge (detached)"},
461+ row{"zut telegram-bot stop", "stop the background bridge"},
462+ row{"zut telegram-bot logs [-f]", "tail the background bridge log"},
463+ row{"zut telegram-bot status", "config + running state"},
464+ row{"zut telegram-bot reset", "forget saved token"},
465+ row{"zut tg ...", "short alias for telegram-bot"},
466466 )
467467 section("provider and model flags",
468468 row{"--provider", "provider to use (anthropic|openai|openai-codex|kimi|deepseek|google|ollama|llama.cpp)"},
@@ -488,7 +488,7 @@ func PrintHelp(version string) {
488488 row{"--no-lsp", "disable the built-in LSP/linter tool"},
489489 row{"--tools csv", "only enable the listed tools (include lsp explicitly)"},
490490 row{"--no-yolo", "ask before running every tool call"},
491- row{"-y, --yes", "accept zot run consent without prompting"},
491+ row{"-y, --yes", "accept zut run consent without prompting"},
492492 row{"--no-ext", "skip extension discovery for this run"},
493493 row{"--no-skill", "skip all skill discovery for this run"},
494494 )
M packages/agent/botcmd.go +63 -63
@@ -14,10 +14,10 @@ import (
1414 "syscall"
1515 "time"
1616
17- "github.com/patriceckhart/zot/packages/agent/modes/bot"
18- "github.com/patriceckhart/zot/packages/agent/modes/telegram"
19- "github.com/patriceckhart/zot/packages/core"
20- "github.com/patriceckhart/zot/packages/provider"
17+ "github.com/bnema/zut/packages/agent/modes/bot"
18+ "github.com/bnema/zut/packages/agent/modes/telegram"
19+ "github.com/bnema/zut/packages/core"
20+ "github.com/bnema/zut/packages/provider"
2121 "golang.org/x/term"
2222 )
2323
@@ -28,7 +28,7 @@ import (
2828 // inherited). See botcmd_unix.go and botcmd_windows.go.
2929 var detachChild func(cmd *exec.Cmd)
3030
31-// runBotCommand dispatches `zot <protocol>-bot ...` subcommands via
31+// runBotCommand dispatches `zut <protocol>-bot ...` subcommands via
3232 // the botSpec registry. Returns true if rawArgs begins with a
3333 // recognised subcommand, false otherwise. The short alias "tg" (and
3434 // "mx" for matrix) are also accepted.
@@ -70,29 +70,29 @@ func runBotCommand(rawArgs []string, version string) (handled bool, err error) {
7070 }
7171 }
7272
73-// botStart launches `zot <spec.subcommand> run` as a detached child
73+// botStart launches `zut <spec.subcommand> run` as a detached child
7474 // process, writes its pid to the spec's pid file, and returns
7575 // immediately. Stdout/stderr of the child are redirected to the spec's
7676 // log file.
7777 func botStart(spec *botSpec, rawTail []string) error {
7878 // Refuse to start if another bot is already running.
79- if pid, alive, _ := bot.IsRunningAt(spec.pidPath(ZotHome())); alive {
80- return fmt.Errorf("%s is already running (pid %d); use `zot %s stop` first", spec.name, pid, spec.subcommand)
79+ if pid, alive, _ := bot.IsRunningAt(spec.pidPath(ZutHome())); alive {
80+ return fmt.Errorf("%s is already running (pid %d); use `zut %s stop` first", spec.name, pid, spec.subcommand)
8181 }
82- _ = bot.RemovePIDFile(spec.pidPath(ZotHome())) // clear any stale pid file
82+ _ = bot.RemovePIDFile(spec.pidPath(ZutHome())) // clear any stale pid file
8383
84- if ok, cerr := spec.configured(ZotHome()); cerr != nil {
84+ if ok, cerr := spec.configured(ZutHome()); cerr != nil {
8585 return cerr
8686 } else if !ok {
87- return fmt.Errorf("%s is not configured — run `zot %s setup` first", spec.name, spec.subcommand)
87+ return fmt.Errorf("%s is not configured — run `zut %s setup` first", spec.name, spec.subcommand)
8888 }
8989
9090 self, err := os.Executable()
9191 if err != nil {
92- return fmt.Errorf("locate zot binary: %w", err)
92+ return fmt.Errorf("locate zut binary: %w", err)
9393 }
9494
95- logPath := spec.logPath(ZotHome())
95+ logPath := spec.logPath(ZutHome())
9696 if err := os.MkdirAll(filepath.Dir(logPath), 0o755); err != nil {
9797 return err
9898 }
@@ -107,11 +107,11 @@ func botStart(spec *botSpec, rawTail []string) error {
107107 // Users hit cryptic tls / exec errors on that path; fail clearly.
108108 if strings.Contains(self, string(os.PathSeparator)+"go-build") ||
109109 strings.Contains(self, string(os.PathSeparator)+"go-tmp") {
110- return fmt.Errorf("detected `go run` temp binary at %s — run `make install` (or copy ./bin/zot to your PATH) and use the installed binary for `start`", self)
110+ return fmt.Errorf("detected `go run` temp binary at %s — run `make install` (or copy ./bin/zut to your PATH) and use the installed binary for `start`", self)
111111 }
112112
113- // Child argv: same flags the user passed to `zot <sub> start`,
114- // mapped to `zot <sub> run`. Preserves --provider, --model, --cwd, etc.
113+ // Child argv: same flags the user passed to `zut <sub> start`,
114+ // mapped to `zut <sub> run`. Preserves --provider, --model, --cwd, etc.
115115 args := append([]string{spec.subcommand, "run"}, rawTail...)
116116 cmd := exec.Command(self, args...)
117117 cmd.Stdout = logFile
@@ -125,28 +125,28 @@ func botStart(spec *botSpec, rawTail []string) error {
125125 if err := cmd.Start(); err != nil {
126126 return fmt.Errorf("spawn: %w", err)
127127 }
128- if err := bot.WritePIDFile(spec.pidPath(ZotHome()), cmd.Process.Pid); err != nil {
128+ if err := bot.WritePIDFile(spec.pidPath(ZutHome()), cmd.Process.Pid); err != nil {
129129 _ = cmd.Process.Kill()
130130 return fmt.Errorf("write pid: %w", err)
131131 }
132132 // Don't wait() — detach. OS will reparent the child to init when we exit.
133133 go func() { _ = cmd.Process.Release() }()
134134
135- fmt.Printf("started zot %s as pid %d (logs: %s)\n", spec.subcommand, cmd.Process.Pid, logPath)
136- fmt.Printf("use `zot %s logs -f` to tail, `zot %s stop` to stop.\n", spec.subcommand, spec.subcommand)
135+ fmt.Printf("started zut %s as pid %d (logs: %s)\n", spec.subcommand, cmd.Process.Pid, logPath)
136+ fmt.Printf("use `zut %s logs -f` to tail, `zut %s stop` to stop.\n", spec.subcommand, spec.subcommand)
137137 return nil
138138 }
139139
140140 // botStop sends SIGTERM to the running bot (SIGKILL if it doesn't
141141 // exit within 5s) and cleans up the pid file.
142142 func botStop(spec *botSpec) error {
143- pid, alive, err := bot.IsRunningAt(spec.pidPath(ZotHome()))
143+ pid, alive, err := bot.IsRunningAt(spec.pidPath(ZutHome()))
144144 if err != nil {
145145 return err
146146 }
147147 if !alive {
148148 if pid > 0 {
149- _ = bot.RemovePIDFile(spec.pidPath(ZotHome()))
149+ _ = bot.RemovePIDFile(spec.pidPath(ZutHome()))
150150 fmt.Printf("no live process; cleared stale pid %d\n", pid)
151151 return nil
152152 }
@@ -156,7 +156,7 @@ func botStop(spec *botSpec) error {
156156 if err := bot.StopProcess(pid, 5*time.Second); err != nil {
157157 return fmt.Errorf("stop pid %d: %w", pid, err)
158158 }
159- _ = bot.RemovePIDFile(spec.pidPath(ZotHome()))
159+ _ = bot.RemovePIDFile(spec.pidPath(ZutHome()))
160160 fmt.Printf("stopped pid %d\n", pid)
161161 return nil
162162 }
@@ -169,7 +169,7 @@ func botLogs(spec *botSpec, rawTail []string) error {
169169 follow = true
170170 }
171171 }
172- p := spec.logPath(ZotHome())
172+ p := spec.logPath(ZutHome())
173173 f, err := os.Open(p)
174174 if errors.Is(err, os.ErrNotExist) {
175175 fmt.Println("no log file at", p)
@@ -220,12 +220,12 @@ func botRun(spec *botSpec, rawTail []string, version string) error {
220220 }
221221 defer closeResolvedLSP(resolved)
222222
223- if ok, cerr := spec.configured(ZotHome()); cerr != nil {
223+ if ok, cerr := spec.configured(ZutHome()); cerr != nil {
224224 return cerr
225225 } else if !ok {
226- return fmt.Errorf("%s is not configured — run `zot %s setup` first", spec.name, spec.subcommand)
226+ return fmt.Errorf("%s is not configured — run `zut %s setup` first", spec.name, spec.subcommand)
227227 }
228- adapter, err := spec.newAdapter(ZotHome())
228+ adapter, err := spec.newAdapter(ZutHome())
229229 if err != nil {
230230 return err
231231 }
@@ -256,7 +256,7 @@ func botRun(spec *botSpec, rawTail []string, version string) error {
256256
257257 var runner *bot.Runner
258258 runner = bot.NewRunner(adapter, agent, bot.Config{
259- ZotHome: ZotHome(),
259+ ZutHome: ZutHome(),
260260 Provider: resolved.Provider,
261261 Model: resolved.Model,
262262 AuthMethod: resolved.AuthMethod,
@@ -278,10 +278,10 @@ func botRun(spec *botSpec, rawTail []string, version string) error {
278278 },
279279 })
280280
281- // Record our pid so `zot <sub> status` / `zot <sub> stop` can find us,
281+ // Record our pid so `zut <sub> status` / `zut <sub> stop` can find us,
282282 // regardless of whether we were started directly or via `start`.
283- _ = bot.WritePIDFile(spec.pidPath(ZotHome()), os.Getpid())
284- defer bot.RemovePIDFile(spec.pidPath(ZotHome()))
283+ _ = bot.WritePIDFile(spec.pidPath(ZutHome()), os.Getpid())
284+ defer bot.RemovePIDFile(spec.pidPath(ZutHome()))
285285
286286 // Translate sigterm/sigint into a context cancel so the bot's goroutines
287287 // and the currently-running turn wind down cleanly.
@@ -300,7 +300,7 @@ func botRun(spec *botSpec, rawTail []string, version string) error {
300300 // but never prompts (no TTY picker); falls back to latest or new.
301301 func openOrCreateSessionForBot(args Args, r Resolved, ag *core.Agent, version string) (*core.Session, []any, error) {
302302 if args.Continue {
303- if latest := core.LatestSession(ZotHome(), args.CWD); latest != "" {
303+ if latest := core.LatestSession(ZutHome(), args.CWD); latest != "" {
304304 s, msgs, err := core.OpenSession(latest)
305305 if err != nil {
306306 return nil, nil, err
@@ -309,11 +309,11 @@ func openOrCreateSessionForBot(args Args, r Resolved, ag *core.Agent, version st
309309 return s, nil, nil
310310 }
311311 }
312- s, err := core.NewSession(ZotHome(), args.CWD, r.Provider, r.Model, version)
312+ s, err := core.NewSession(ZutHome(), args.CWD, r.Provider, r.Model, version)
313313 return s, nil, err
314314 }
315315
316-// maskToken returns "123456:ABC...xyz" so copies of zot telegram-bot status can be
316+// maskToken returns "123456:ABC...xyz" so copies of zut telegram-bot status can be
317317 // pasted into bug reports without leaking the full token.
318318 func maskToken(tok string) string {
319319 if len(tok) <= 10 {
@@ -341,8 +341,8 @@ func telegramSpec() *botSpec {
341341 aliases: []string{"tg"},
342342 pidPath: telegram.PIDPath,
343343 logPath: telegram.LogPath,
344- configured: func(zotHome string) (bool, error) {
345- cfg, err := telegram.LoadConfig(zotHome)
344+ configured: func(zutHome string) (bool, error) {
345+ cfg, err := telegram.LoadConfig(zutHome)
346346 if err != nil {
347347 return false, err
348348 }
@@ -352,37 +352,37 @@ func telegramSpec() *botSpec {
352352 setup: telegramBotSetup,
353353 status: telegramBotStatus,
354354 reset: telegramBotReset,
355- newAdapter: func(zotHome string) (bot.BotAdapter, error) {
356- cfg, err := telegram.LoadConfig(zotHome)
355+ newAdapter: func(zutHome string) (bot.BotAdapter, error) {
356+ cfg, err := telegram.LoadConfig(zutHome)
357357 if err != nil {
358358 return nil, err
359359 }
360360 return telegram.NewAdapter(
361361 telegram.NewClient(cfg.BotToken),
362362 &cfg,
363- func(c telegram.Config) error { return telegram.SaveConfig(zotHome, c) },
363+ func(c telegram.Config) error { return telegram.SaveConfig(zutHome, c) },
364364 ), nil
365365 },
366366 }
367367 }
368368
369-// printTelegramBotHelp prints usage for `zot telegram-bot`.
369+// printTelegramBotHelp prints usage for `zut telegram-bot`.
370370 func printTelegramBotHelp() {
371- fmt.Fprint(os.Stderr, `zot telegram-bot — telegram bridge
371+ fmt.Fprint(os.Stderr, `zut telegram-bot — telegram bridge
372372
373373 usage:
374- zot telegram-bot setup paste a BotFather token, verify, save
375- zot telegram-bot status show bridge config and whether it's running
376- zot telegram-bot run [flags] run in the foreground (ctrl+c to stop)
377- zot telegram-bot start [flags] launch in background, detach, return immediately
378- zot telegram-bot stop sigterm the running background bot, sigkill if needed
379- zot telegram-bot logs [--follow] tail the background bot's log file
380- zot telegram-bot reset forget token + allowed user
374+ zut telegram-bot setup paste a BotFather token, verify, save
375+ zut telegram-bot status show bridge config and whether it's running
376+ zut telegram-bot run [flags] run in the foreground (ctrl+c to stop)
377+ zut telegram-bot start [flags] launch in background, detach, return immediately
378+ zut telegram-bot stop sigterm the running background bot, sigkill if needed
379+ zut telegram-bot logs [--follow] tail the background bot's log file
380+ zut telegram-bot reset forget token + allowed user
381381
382382 setup flow:
383383 1. talk to @BotFather on telegram, /newbot, copy the token
384- 2. run "zot telegram-bot setup" and paste the token
385- 3. run "zot telegram-bot start" (background) or "zot telegram-bot run" (foreground)
384+ 2. run "zut telegram-bot setup" and paste the token
385+ 3. run "zut telegram-bot start" (background) or "zut telegram-bot run" (foreground)
386386 4. send /start to your bot from telegram; the first sender claims it
387387
388388 while the bot is running, dm it anything and the message is forwarded
@@ -391,9 +391,9 @@ to the agent the same way it would be from the tui. image attachments
391391 telegram commands the bot handles directly: /help, /status, /stop.
392392
393393 config & state:
394- $ZOT_HOME/bot.json # bot token + paired user (mode 0600)
395- $ZOT_HOME/bot.pid # pid of the running bot (written by run/start)
396- $ZOT_HOME/logs/bot.log # stdout+stderr from "zot telegram-bot start"
394+ $ZUT_HOME/bot.json # bot token + paired user (mode 0600)
395+ $ZUT_HOME/bot.pid # pid of the running bot (written by run/start)
396+ $ZUT_HOME/logs/bot.log # stdout+stderr from "zut telegram-bot start"
397397 `)
398398 }
399399
@@ -422,7 +422,7 @@ func readBotToken(in *os.File, out io.Writer) (string, error) {
422422 // telegramBotSetup interactively reads a bot token without echoing it on a
423423 // terminal, verifies it via getMe, and saves it.
424424 func telegramBotSetup(_ []string) error {
425- cfg, err := telegram.LoadConfig(ZotHome())
425+ cfg, err := telegram.LoadConfig(ZutHome())
426426 if err != nil {
427427 return err
428428 }
@@ -448,23 +448,23 @@ func telegramBotSetup(_ []string) error {
448448 // Any stored pairing might be for a different bot; clear it.
449449 cfg.AllowedUserID = 0
450450 cfg.LastUpdateID = 0
451- if err := telegram.SaveConfig(ZotHome(), cfg); err != nil {
451+ if err := telegram.SaveConfig(ZutHome(), cfg); err != nil {
452452 return err
453453 }
454- fmt.Printf("\nsaved: @%s (id=%d) to %s\n", me.Username, me.ID, telegram.ConfigPath(ZotHome()))
455- fmt.Println("next: run `zot telegram-bot run`, then send /start to your bot from telegram.")
454+ fmt.Printf("\nsaved: @%s (id=%d) to %s\n", me.Username, me.ID, telegram.ConfigPath(ZutHome()))
455+ fmt.Println("next: run `zut telegram-bot run`, then send /start to your bot from telegram.")
456456 return nil
457457 }
458458
459459 // telegramBotStatus prints the current bot config without the token, plus
460460 // whether the background process is alive.
461461 func telegramBotStatus() error {
462- cfg, err := telegram.LoadConfig(ZotHome())
462+ cfg, err := telegram.LoadConfig(ZutHome())
463463 if err != nil {
464464 return err
465465 }
466466 if cfg.BotToken == "" {
467- fmt.Println("telegram: not configured (run `zot telegram-bot setup`)")
467+ fmt.Println("telegram: not configured (run `zut telegram-bot setup`)")
468468 return nil
469469 }
470470 maskedTok := maskToken(cfg.BotToken)
@@ -476,18 +476,18 @@ func telegramBotStatus() error {
476476 fmt.Printf("paired with: telegram user id %d\n", cfg.AllowedUserID)
477477 }
478478 fmt.Printf("last update: %d\n", cfg.LastUpdateID)
479- fmt.Printf("config file: %s\n", telegram.ConfigPath(ZotHome()))
479+ fmt.Printf("config file: %s\n", telegram.ConfigPath(ZutHome()))
480480
481- pid, alive, _ := telegram.IsRunning(ZotHome())
481+ pid, alive, _ := telegram.IsRunning(ZutHome())
482482 switch {
483483 case alive:
484484 fmt.Printf("process: running (pid %d)\n", pid)
485485 case pid > 0:
486- fmt.Printf("process: stopped (stale pid %d in %s)\n", pid, telegram.PIDPath(ZotHome()))
486+ fmt.Printf("process: stopped (stale pid %d in %s)\n", pid, telegram.PIDPath(ZutHome()))
487487 default:
488488 fmt.Println("process: stopped")
489489 }
490- logPath := telegram.LogPath(ZotHome())
490+ logPath := telegram.LogPath(ZutHome())
491491 if fi, err := os.Stat(logPath); err == nil {
492492 fmt.Printf("log file: %s (%d bytes)\n", logPath, fi.Size())
493493 }
@@ -496,7 +496,7 @@ func telegramBotStatus() error {
496496
497497 // telegramBotReset wipes the on-disk bot.json entry.
498498 func telegramBotReset() error {
499- p := telegram.ConfigPath(ZotHome())
499+ p := telegram.ConfigPath(ZutHome())
500500 if _, err := os.Stat(p); os.IsNotExist(err) {
501501 fmt.Println("no bot config to reset")
502502 return nil
M packages/agent/botspec.go +5 -5
@@ -1,7 +1,7 @@
11 package agent
22
33 import (
4- "github.com/patriceckhart/zot/packages/agent/modes/bot"
4+ "github.com/bnema/zut/packages/agent/modes/bot"
55 )
66
77 // botSpec describes one bot protocol to the generic daemon CLI in
@@ -13,12 +13,12 @@ type botSpec struct {
1313 subcommand string // "telegram-bot", "matrix-bot"
1414 aliases []string // {"tg"}, {"mx"}
1515
16- pidPath func(zotHome string) string
17- logPath func(zotHome string) string
16+ pidPath func(zutHome string) string
17+ logPath func(zutHome string) string
1818
1919 // configured reports whether setup has been completed (e.g. a
2020 // token is present), with a hint error message when it hasn't.
21- configured func(zotHome string) (bool, error)
21+ configured func(zutHome string) (bool, error)
2222
2323 printHelp func()
2424 setup func(tail []string) error
@@ -27,7 +27,7 @@ type botSpec struct {
2727
2828 // newAdapter builds the protocol adapter for the standalone
2929 // daemon. Credential refresh stays generic in botRun.
30- newAdapter func(zotHome string) (bot.BotAdapter, error)
30+ newAdapter func(zutHome string) (bot.BotAdapter, error)
3131 }
3232
3333 // botSpecs is the registry the dispatcher walks. Matrix is appended
M packages/agent/build.go +31 -31
@@ -8,13 +8,13 @@ import (
88 "runtime"
99 "strings"
1010
11- zotdocs "github.com/patriceckhart/zot"
12- "github.com/patriceckhart/zot/packages/agent/lsp"
13- "github.com/patriceckhart/zot/packages/agent/skills"
14- "github.com/patriceckhart/zot/packages/agent/subagents"
15- "github.com/patriceckhart/zot/packages/agent/tools"
16- "github.com/patriceckhart/zot/packages/core"
17- "github.com/patriceckhart/zot/packages/provider"
11+ zutdocs "github.com/bnema/zut"
12+ "github.com/bnema/zut/packages/agent/lsp"
13+ "github.com/bnema/zut/packages/agent/skills"
14+ "github.com/bnema/zut/packages/agent/subagents"
15+ "github.com/bnema/zut/packages/agent/tools"
16+ "github.com/bnema/zut/packages/core"
17+ "github.com/bnema/zut/packages/provider"
1818 )
1919
2020 // ContextFile is an instruction file loaded into the system prompt.
@@ -136,7 +136,7 @@ func (r *Resolved) MergeExtensionTools(mgr ExtensionToolSource) {
136136 Tools: r.ToolSummary,
137137 Custom: r.systemCustom,
138138 Append: r.systemAppend,
139- ZotDocsDir: filepath.Join(ZotHome(), "docs"),
139+ ZutDocsDir: filepath.Join(ZutHome(), "docs"),
140140 })
141141 }
142142
@@ -217,7 +217,7 @@ func toolSummariesFromRegistry(reg core.Registry, cached map[string]string) []To
217217 return out
218218 }
219219
220-// defaultModelForProvider returns the model id zot prefers when the
220+// defaultModelForProvider returns the model id zut prefers when the
221221 // caller didn't pick one. Mirrors the per-provider switch used at
222222 // multiple points in Resolve; centralised so the unknown-model
223223 // recovery path and the no-model-configured path can't drift.
@@ -289,7 +289,7 @@ func defaultModelForProvider(prov string) string {
289289 }
290290 }
291291
292-// knownProviders is the set of provider ids zot recognises. Used by
292+// knownProviders is the set of provider ids zut recognises. Used by
293293 // Resolve to validate args.Provider, by extension-callers, and by the
294294 // auto-fallback logic that picks any logged-in provider when the user's
295295 // preferred one has no credentials.
@@ -495,7 +495,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
495495 // If the user did NOT explicitly pick a provider (neither via --provider
496496 // nor by saving one in config.json) and the default one has no
497497 // credentials, auto-fall-back to whichever provider is actually logged
498- // in. That way running plain `zot` after `/login` (any provider) never
498+ // in. That way running plain `zut` after `/login` (any provider) never
499499 // shows a "not logged in" banner.
500500 //
501501 // Critical: when the user HAS saved a provider in config.json (e.g.
@@ -518,7 +518,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
518518 // env-based credential is discovered, e.g. an env-only
519519 // amazon-bedrock setup (AWS_BEARER_TOKEN_BEDROCK / AWS_PROFILE /
520520 // IAM keys) when no config.json pins the provider, such as after
521- // pointing ZOT_HOME at a fresh home dir. Iteration order of
521+ // pointing ZUT_HOME at a fresh home dir. Iteration order of
522522 // knownProviders defines fallback priority. Local providers without a
523523 // default model are skipped because selecting either one here would
524524 // fail before the user can choose a model.
@@ -591,7 +591,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
591591 if err != nil {
592592 // The model the user (or persisted config) asked for is no
593593 // longer in the active catalogue — they probably removed it
594- // from their models.json or upgraded zot and the id changed.
594+ // from their models.json or upgraded zut and the id changed.
595595 // Refusing to launch is the wrong move: it strands the user
596596 // with no way to even open the TUI and pick a new model.
597597 // Fall back to the provider's default, warn on stderr, and,
@@ -600,7 +600,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
600600 // doesn't repeat on every launch.
601601
602602 // Gateway providers can accept route-qualified ids that are not in
603- // zot's local catalog yet, for example OpenRouter's
603+ // zut's local catalog yet, for example OpenRouter's
604604 // "deepseek/deepseek-v4-flash". Preserve only route-qualified ids;
605605 // plain unknown values are likely typos and should still fall back.
606606 if isGatewayProvider(provName) && isGatewayRoutedModelID(model) {
@@ -628,7 +628,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
628628 }
629629 }
630630 fmt.Fprintf(os.Stderr,
631- "zot: model %q is not in the active catalogue; using %q instead. Pick a different model with --model or /model.\n",
631+ "zut: model %q is not in the active catalogue; using %q instead. Pick a different model with --model or /model.\n",
632632 model, fm.ID)
633633 if args.Model == "" && cfg.Model == model {
634634 cfg.Model = fm.ID
@@ -677,7 +677,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
677677 }
678678
679679 if credErr != nil && requireCred {
680- return Resolved{}, fmt.Errorf("%w; set %s_API_KEY, pass --api-key, or run `zot` and /login",
680+ return Resolved{}, fmt.Errorf("%w; set %s_API_KEY, pass --api-key, or run `zut` and /login",
681681 credErr, envVarName(provName))
682682 }
683683
@@ -692,7 +692,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
692692 lspEnabled := !args.NoLSP && cfg.LSPEnabledFor(subagentSession)
693693 reg := buildToolRegistry(args, args.CWD, sandbox, lspEnabled, cfg.LSPDiagnosticsOnWriteEnabled(subagentSession), cfg.LSPDiagnosticsOnEditEnabled(subagentSession))
694694
695- docsDir, _ := zotdocs.EnsureInstalled(ZotHome())
695+ docsDir, _ := zutdocs.EnsureInstalled(ZutHome())
696696
697697 // Skill discovery: scan project + global locations + built-in
698698 // skills shipped with the binary. If any are found, register
@@ -711,7 +711,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
711711 if !args.NoSkill && (selectedProfile == nil || selectedProfile.InheritSkills == nil || *selectedProfile.InheritSkills) {
712712 skillsEnabled = true
713713 homeDir, _ := os.UserHomeDir()
714- discovered, _ = skills.Discover(ZotHome(), args.CWD, homeDir, args.WithSkills)
714+ discovered, _ = skills.Discover(ZutHome(), args.CWD, homeDir, args.WithSkills)
715715 if len(discovered) > 0 {
716716 skillTool = skills.NewTool(discovered)
717717 reg[skillTool.Name()] = skillTool
@@ -724,7 +724,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
724724
725725 contextFiles := []ContextFile(nil)
726726 if selectedProfile == nil || selectedProfile.InheritProjectContext == nil || *selectedProfile.InheritProjectContext {
727- contextFiles = loadAgentsContext(args.CWD, ZotHome())
727+ contextFiles = loadAgentsContext(args.CWD, ZutHome())
728728 }
729729 append_ := []string(nil)
730730 if cfg.PonytailModeEnabled() {
@@ -754,13 +754,13 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
754754
755755 // Custom system prompt resolution order:
756756 // 1. --system-prompt flag (highest priority; ad-hoc per run)
757- // 2. $ZOT_HOME/SYSTEM.md (persistent user override)
757+ // 2. $ZUT_HOME/SYSTEM.md (persistent user override)
758758 // 3. built-in default (defaultIdentity + defaultGuidelines)
759759 custom := args.SystemPrompt
760760 if selectedProfile != nil && selectedProfile.SystemPromptMode == "replace" {
761761 custom = selectedProfile.SystemPrompt
762762 } else if custom == "" {
763- custom = readUserSystemPrompt(ZotHome())
763+ custom = readUserSystemPrompt(ZutHome())
764764 }
765765
766766 sys := BuildSystemPrompt(SystemPromptOpts{
@@ -768,7 +768,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
768768 Tools: summaries,
769769 Custom: custom,
770770 Append: append_,
771- ZotDocsDir: docsDir,
771+ ZutDocsDir: docsDir,
772772 })
773773
774774 reasoning := provider.NormalizeReasoning(firstNonEmpty(args.Reasoning, cfg.Reasoning))
@@ -818,16 +818,16 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
818818 }, nil
819819 }
820820
821-// readUserSystemPrompt looks for $ZOT_HOME/SYSTEM.md and returns its
821+// readUserSystemPrompt looks for $ZUT_HOME/SYSTEM.md and returns its
822822 // trimmed contents, or "" when the file is missing / unreadable /
823823 // empty. Errors are intentionally swallowed: the file is optional,
824824 // and any failure to read it should fall back to the built-in
825825 // default system prompt rather than crash the run.
826-func readUserSystemPrompt(zotHome string) string {
827- if zotHome == "" {
826+func readUserSystemPrompt(zutHome string) string {
827+ if zutHome == "" {
828828 return ""
829829 }
830- path := filepath.Join(zotHome, "SYSTEM.md")
830+ path := filepath.Join(zutHome, "SYSTEM.md")
831831 raw, err := os.ReadFile(path)
832832 if err != nil {
833833 return ""
@@ -836,10 +836,10 @@ func readUserSystemPrompt(zotHome string) string {
836836 }
837837
838838 // loadAgentsContext loads optional AGENTS.md instruction files. No
839-// default file is created or required: zot only includes files that
840-// already exist. Global instructions ($ZOT_HOME/AGENTS.md) come first,
839+// default file is created or required: zut only includes files that
840+// already exist. Global instructions ($ZUT_HOME/AGENTS.md) come first,
841841 // followed by project instructions from the filesystem root down to cwd.
842-func loadAgentsContext(cwd, zotHome string) []ContextFile {
842+func loadAgentsContext(cwd, zutHome string) []ContextFile {
843843 var files []ContextFile
844844 seen := map[string]bool{}
845845 add := func(path string) {
@@ -877,7 +877,7 @@ func loadAgentsContext(cwd, zotHome string) []ContextFile {
877877 }
878878 }
879879
880- addFirstFromDir(zotHome)
880+ addFirstFromDir(zutHome)
881881
882882 if cwd != "" {
883883 abs, err := filepath.Abs(cwd)
@@ -1250,7 +1250,7 @@ func kimiCodeHeaders() map[string]string {
12501250 }
12511251 }
12521252 if deviceID == "" {
1253- deviceID = "zot"
1253+ deviceID = "zut"
12541254 }
12551255 return map[string]string{
12561256 "User-Agent": "KimiCLI/1.41.0",
M packages/agent/build_test.go +37 -37
@@ -10,10 +10,10 @@ import (
1010 "strings"
1111 "testing"
1212
13- "github.com/patriceckhart/zot/packages/agent/skills"
14- "github.com/patriceckhart/zot/packages/agent/tools"
15- "github.com/patriceckhart/zot/packages/core"
16- "github.com/patriceckhart/zot/packages/provider"
13+ "github.com/bnema/zut/packages/agent/skills"
14+ "github.com/bnema/zut/packages/agent/tools"
15+ "github.com/bnema/zut/packages/core"
16+ "github.com/bnema/zut/packages/provider"
1717 )
1818
1919 type bundledSkillSource struct {
@@ -140,16 +140,16 @@ func TestAutoSubagentsToolPoliciesTrackEachTool(t *testing.T) {
140140
141141 func TestReadAgentsContextLoadsGlobalAndAncestors(t *testing.T) {
142142 root := t.TempDir()
143- zotHome := filepath.Join(root, "zot-home")
143+ zutHome := filepath.Join(root, "zut-home")
144144 project := filepath.Join(root, "repo")
145145 nested := filepath.Join(project, "packages", "app")
146- if err := os.MkdirAll(zotHome, 0o755); err != nil {
146+ if err := os.MkdirAll(zutHome, 0o755); err != nil {
147147 t.Fatal(err)
148148 }
149149 if err := os.MkdirAll(nested, 0o755); err != nil {
150150 t.Fatal(err)
151151 }
152- if err := os.WriteFile(filepath.Join(zotHome, "AGENTS.md"), []byte("global rule"), 0o644); err != nil {
152+ if err := os.WriteFile(filepath.Join(zutHome, "AGENTS.md"), []byte("global rule"), 0o644); err != nil {
153153 t.Fatal(err)
154154 }
155155 if err := os.WriteFile(filepath.Join(project, "AGENTS.md"), []byte("repo rule"), 0o644); err != nil {
@@ -159,12 +159,12 @@ func TestReadAgentsContextLoadsGlobalAndAncestors(t *testing.T) {
159159 t.Fatal(err)
160160 }
161161
162- files := loadAgentsContext(nested, zotHome)
162+ files := loadAgentsContext(nested, zutHome)
163163 if len(files) != 3 {
164164 t.Fatalf("loaded %d context files, want 3: %#v", len(files), files)
165165 }
166166 wantPaths := []string{
167- filepath.Join(zotHome, "AGENTS.md"),
167+ filepath.Join(zutHome, "AGENTS.md"),
168168 filepath.Join(project, "AGENTS.md"),
169169 filepath.Join(nested, "AGENTS.md"),
170170 }
@@ -190,7 +190,7 @@ func TestFindSubagentProfileReportsDiscoveryFailure(t *testing.T) {
190190 if err := os.WriteFile(profileSource, []byte("not a directory"), 0o600); err != nil {
191191 t.Fatal(err)
192192 }
193- t.Setenv("ZOT_AGENT_PROFILES", profileSource)
193+ t.Setenv("ZUT_AGENT_PROFILES", profileSource)
194194
195195 profile, err := findSubagentProfile("", "reviewer")
196196 if profile != nil {
@@ -207,7 +207,7 @@ func TestFindSubagentProfileReportsDiscoveryFailure(t *testing.T) {
207207 func TestResolveIncludesNamedSubagentsListWhenAutoSubagentsIsEnabled(t *testing.T) {
208208 root := t.TempDir()
209209 home := filepath.Join(root, "home")
210- zotHome := filepath.Join(root, "zot-home")
210+ zutHome := filepath.Join(root, "zut-home")
211211 project := filepath.Join(root, "repo")
212212 if err := os.MkdirAll(filepath.Join(home, ".agents", "agents"), 0o755); err != nil {
213213 t.Fatal(err)
@@ -216,8 +216,8 @@ func TestResolveIncludesNamedSubagentsListWhenAutoSubagentsIsEnabled(t *testing.
216216 t.Fatal(err)
217217 }
218218 t.Setenv("HOME", home)
219- t.Setenv("ZOT_HOME", zotHome)
220- t.Setenv("ZOT_AGENT_PROFILES", filepath.Join(home, ".agents", "agents"))
219+ t.Setenv("ZUT_HOME", zutHome)
220+ t.Setenv("ZUT_AGENT_PROFILES", filepath.Join(home, ".agents", "agents"))
221221 enabled := true
222222 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", AutoSubagentsEnabled: &enabled}); err != nil {
223223 t.Fatal(err)
@@ -247,7 +247,7 @@ Review without editing.
247247 func TestResolveAppliesSelectedSubagentProfile(t *testing.T) {
248248 root := t.TempDir()
249249 home := filepath.Join(root, "home")
250- zotHome := filepath.Join(root, "zot-home")
250+ zutHome := filepath.Join(root, "zut-home")
251251 project := filepath.Join(root, "repo")
252252 if err := os.MkdirAll(filepath.Join(home, ".agents", "agents"), 0o755); err != nil {
253253 t.Fatal(err)
@@ -256,12 +256,12 @@ func TestResolveAppliesSelectedSubagentProfile(t *testing.T) {
256256 t.Fatal(err)
257257 }
258258 t.Setenv("HOME", home)
259- t.Setenv("ZOT_HOME", zotHome)
260- t.Setenv("ZOT_AGENT_PROFILES", filepath.Join(home, ".agents", "agents"))
259+ t.Setenv("ZUT_HOME", zutHome)
260+ t.Setenv("ZUT_AGENT_PROFILES", filepath.Join(home, ".agents", "agents"))
261261 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", Reasoning: "high"}); err != nil {
262262 t.Fatal(err)
263263 }
264- if err := os.WriteFile(filepath.Join(zotHome, "AGENTS.md"), []byte("global context"), 0o600); err != nil {
264+ if err := os.WriteFile(filepath.Join(zutHome, "AGENTS.md"), []byte("global context"), 0o600); err != nil {
265265 t.Fatal(err)
266266 }
267267 profile := `---
@@ -320,7 +320,7 @@ func TestResolveSubagentFastModeUsesHostSettingAsAnUpperBound(t *testing.T) {
320320 t.Run(tc.name, func(t *testing.T) {
321321 root := t.TempDir()
322322 home := filepath.Join(root, "home")
323- zotHome := filepath.Join(root, "zot-home")
323+ zutHome := filepath.Join(root, "zut-home")
324324 project := filepath.Join(root, "repo")
325325 profilesDir := filepath.Join(home, ".agents", "agents")
326326 if err := os.MkdirAll(profilesDir, 0o755); err != nil {
@@ -330,8 +330,8 @@ func TestResolveSubagentFastModeUsesHostSettingAsAnUpperBound(t *testing.T) {
330330 t.Fatal(err)
331331 }
332332 t.Setenv("HOME", home)
333- t.Setenv("ZOT_HOME", zotHome)
334- t.Setenv("ZOT_AGENT_PROFILES", profilesDir)
333+ t.Setenv("ZUT_HOME", zutHome)
334+ t.Setenv("ZUT_AGENT_PROFILES", profilesDir)
335335
336336 cfg := Config{Provider: "openai", Model: "gpt-5"}
337337 cfg.FastMode = &tc.hostFastMode
@@ -376,12 +376,12 @@ func TestReadAgentsContextMissingFilesIsEmpty(t *testing.T) {
376376 // TestResolveFallsBackWhenConfiguredModelIsGone reproduces the
377377 // startup failure caught by the user's screenshot: the persisted
378378 // config.json points at a model id that's no longer in the active
379-// catalogue (because they edited models.json or zot's bundled
379+// catalogue (because they edited models.json or zut's bundled
380380 // catalogue changed). Resolve must NOT error — strands the user
381381 // with no way to fix it from the TUI — and should repair the config
382382 // so the next launch is silent.
383383 func TestResolveFallsBackWhenConfiguredModelIsGone(t *testing.T) {
384- t.Setenv("ZOT_HOME", t.TempDir())
384+ t.Setenv("ZUT_HOME", t.TempDir())
385385 t.Setenv("OPENAI_API_KEY", "test-key")
386386 // Persist a stale model id.
387387 stale := "gpt-5.5-pro-not-real"
@@ -421,7 +421,7 @@ func TestResolveAppliesJailByDefault(t *testing.T) {
421421 {name: "enabled starts locked", config: Config{Provider: "openai", Model: "gpt-5", JailByDefault: boolPtr(true)}, want: true},
422422 } {
423423 t.Run(tc.name, func(t *testing.T) {
424- t.Setenv("ZOT_HOME", t.TempDir())
424+ t.Setenv("ZUT_HOME", t.TempDir())
425425 t.Setenv("OPENAI_API_KEY", "test-key")
426426 if err := SaveConfig(tc.config); err != nil {
427427 t.Fatal(err)
@@ -445,7 +445,7 @@ func boolPtr(v bool) *bool { return &v }
445445 // persisted config. If the user passed --model X explicitly and X is
446446 // unknown, we still fall back, but we don't touch their config.
447447 func TestResolveExplicitFlagStaleDoesNotRepairConfig(t *testing.T) {
448- t.Setenv("ZOT_HOME", t.TempDir())
448+ t.Setenv("ZUT_HOME", t.TempDir())
449449 t.Setenv("OPENAI_API_KEY", "test-key")
450450 good := "gpt-5"
451451 if err := SaveConfig(Config{Provider: "openai", Model: good}); err != nil {
@@ -466,7 +466,7 @@ func TestResolveExplicitFlagStaleDoesNotRepairConfig(t *testing.T) {
466466 }
467467
468468 func TestResolveOpenRouterPreservesSavedRoutedModelID(t *testing.T) {
469- t.Setenv("ZOT_HOME", t.TempDir())
469+ t.Setenv("ZUT_HOME", t.TempDir())
470470 t.Setenv("OPENROUTER_API_KEY", "test-key")
471471 want := "deepseek/deepseek-v4-flash"
472472 if err := SaveConfig(Config{Provider: "openrouter", Model: want}); err != nil {
@@ -489,7 +489,7 @@ func TestResolveOpenRouterPreservesSavedRoutedModelID(t *testing.T) {
489489 }
490490
491491 func TestResolveGatewayPlainUnknownModelFallsBack(t *testing.T) {
492- t.Setenv("ZOT_HOME", t.TempDir())
492+ t.Setenv("ZUT_HOME", t.TempDir())
493493 t.Setenv("OPENROUTER_API_KEY", "test-key")
494494 stale := "not-a-routed-model"
495495 if err := SaveConfig(Config{Provider: "openrouter", Model: stale}); err != nil {
@@ -509,12 +509,12 @@ func TestResolveGatewayPlainUnknownModelFallsBack(t *testing.T) {
509509 }
510510
511511 // TestResolveEnvOnlyBedrockDiscoveredWithoutConfig reproduces issue
512-// #15: pointing ZOT_HOME at a fresh dir drops the persisted
512+// #15: pointing ZUT_HOME at a fresh dir drops the persisted
513513 // config.json (which pinned provider=amazon-bedrock). Resolve must
514514 // still discover bedrock from the AWS env vars instead of falling back
515515 // to anthropic and reporting "not logged in".
516516 func TestResolveEnvOnlyBedrockDiscoveredWithoutConfig(t *testing.T) {
517- t.Setenv("ZOT_HOME", t.TempDir()) // fresh home: no config.json
517+ t.Setenv("ZUT_HOME", t.TempDir()) // fresh home: no config.json
518518 // Disable the Kimi CLI token fallback so a developer machine with a
519519 // real Kimi CLI login doesn't pre-empt bedrock in the scan.
520520 if err := SetKimiCLIFallbackDisabled(true); err != nil {
@@ -540,7 +540,7 @@ func TestResolveEnvOnlyBedrockDiscoveredWithoutConfig(t *testing.T) {
540540 }
541541
542542 func TestResolveOllamaUsesModelBaseURLBeforeDefault(t *testing.T) {
543- t.Setenv("ZOT_HOME", t.TempDir())
543+ t.Setenv("ZUT_HOME", t.TempDir())
544544 provider.SetLiveModels(nil)
545545 defer provider.SetLiveModels(nil)
546546 provider.SetUserModels([]provider.Model{{
@@ -562,7 +562,7 @@ func TestResolveOllamaUsesModelBaseURLBeforeDefault(t *testing.T) {
562562 }
563563
564564 func TestResolveUsesInheritedSupervisorCredential(t *testing.T) {
565- t.Setenv("ZOT_HOME", t.TempDir())
565+ t.Setenv("ZUT_HOME", t.TempDir())
566566 t.Setenv("OPENAI_API_KEY", "")
567567
568568 r, err := Resolve(Args{
@@ -581,7 +581,7 @@ func TestResolveUsesInheritedSupervisorCredential(t *testing.T) {
581581 }
582582
583583 func TestResolveLlamaCPPUsesRouterInferenceURL(t *testing.T) {
584- t.Setenv("ZOT_HOME", t.TempDir())
584+ t.Setenv("ZUT_HOME", t.TempDir())
585585 t.Setenv("LLAMA_BASE_URL", "http://127.0.0.1:8080/v1/")
586586 t.Setenv("LLAMA_API_KEY", "")
587587 provider.SetManagedModels(nil)
@@ -603,7 +603,7 @@ func TestResolveLlamaCPPUsesRouterInferenceURL(t *testing.T) {
603603 }
604604
605605 func TestResolveLlamaCPPUsesStoredLogin(t *testing.T) {
606- t.Setenv("ZOT_HOME", t.TempDir())
606+ t.Setenv("ZUT_HOME", t.TempDir())
607607 t.Setenv("LLAMA_BASE_URL", "")
608608 t.Setenv("LLAMA_API_KEY", "")
609609 if err := AuthStoreFor().SetEndpointCredential("llama.cpp", "http://localhost:9090", "stored-key"); err != nil {
@@ -620,7 +620,7 @@ func TestResolveLlamaCPPUsesStoredLogin(t *testing.T) {
620620 }
621621
622622 func TestResolveCustomProviderModelBaseURLBeatsProviderBaseURL(t *testing.T) {
623- t.Setenv("ZOT_HOME", t.TempDir())
623+ t.Setenv("ZUT_HOME", t.TempDir())
624624 t.Setenv("MY_COMPANY_API_KEY", "test-key")
625625 path := filepath.Join(t.TempDir(), "models.json")
626626 if err := os.WriteFile(path, []byte(`{
@@ -702,7 +702,7 @@ func TestCustomProviderUsesOpenAIResponsesAPI(t *testing.T) {
702702 }
703703
704704 func TestResolveCustomProviderInsecureFromModelsJSONBaseURL(t *testing.T) {
705- t.Setenv("ZOT_HOME", t.TempDir())
705+ t.Setenv("ZUT_HOME", t.TempDir())
706706 t.Setenv("LOCAL_PROXY_API_KEY", "test-key")
707707 path := filepath.Join(t.TempDir(), "models.json")
708708 if err := os.WriteFile(path, []byte(`{
@@ -734,7 +734,7 @@ func TestResolveCustomProviderInsecureFromModelsJSONBaseURL(t *testing.T) {
734734 }
735735
736736 func TestResolveOllamaFallsBackToDefaultBaseURL(t *testing.T) {
737- t.Setenv("ZOT_HOME", t.TempDir())
737+ t.Setenv("ZUT_HOME", t.TempDir())
738738 provider.SetLiveModels(nil)
739739 defer provider.SetLiveModels(nil)
740740
@@ -785,7 +785,7 @@ func TestResolveInsecureOnlyWithExplicitBaseURL(t *testing.T) {
785785 orig := http.DefaultTransport
786786 t.Cleanup(func() { http.DefaultTransport = orig })
787787
788- t.Setenv("ZOT_HOME", t.TempDir())
788+ t.Setenv("ZUT_HOME", t.TempDir())
789789 t.Setenv("OPENAI_API_KEY", "test-key")
790790
791791 resolved, err := Resolve(Args{Provider: "moonshotai", InsecureTLS: true}, false)
@@ -811,7 +811,7 @@ func TestResolveInsecureFromConfigRequiresExplicitBaseURL(t *testing.T) {
811811 orig := http.DefaultTransport
812812 t.Cleanup(func() { http.DefaultTransport = orig })
813813
814- t.Setenv("ZOT_HOME", t.TempDir())
814+ t.Setenv("ZUT_HOME", t.TempDir())
815815 t.Setenv("OPENAI_API_KEY", "test-key")
816816 if err := SaveConfig(Config{Provider: "openai", Insecure: true}); err != nil {
817817 t.Fatal(err)
M packages/agent/changelog.go +1 -1
@@ -45,7 +45,7 @@ func FetchChangelog(ctx context.Context, version string) (ChangelogInfo, error)
4545 if !strings.HasPrefix(tag, "v") {
4646 tag = "v" + tag
4747 }
48- url := fmt.Sprintf("https://api.github.com/repos/patriceckhart/zot/releases/tags/%s", tag)
48+ url := fmt.Sprintf("https://api.github.com/repos/bnema/zut/releases/tags/%s", tag)
4949 req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
5050 if err != nil {
5151 return ChangelogInfo{}, err
M packages/agent/cli.go +45 -45
@@ -16,16 +16,16 @@ import (
1616 "syscall"
1717 "time"
1818
19- "github.com/patriceckhart/zot/packages/agent/extensions"
20- "github.com/patriceckhart/zot/packages/agent/extproto"
21- "github.com/patriceckhart/zot/packages/agent/modes"
22- "github.com/patriceckhart/zot/packages/agent/skills"
23- "github.com/patriceckhart/zot/packages/agent/subagents"
24- "github.com/patriceckhart/zot/packages/agent/tools"
25- "github.com/patriceckhart/zot/packages/core"
26- "github.com/patriceckhart/zot/packages/provider"
27- "github.com/patriceckhart/zot/packages/provider/auth"
28- "github.com/patriceckhart/zot/packages/tui"
19+ "github.com/bnema/zut/packages/agent/extensions"
20+ "github.com/bnema/zut/packages/agent/extproto"
21+ "github.com/bnema/zut/packages/agent/modes"
22+ "github.com/bnema/zut/packages/agent/skills"
23+ "github.com/bnema/zut/packages/agent/subagents"
24+ "github.com/bnema/zut/packages/agent/tools"
25+ "github.com/bnema/zut/packages/core"
26+ "github.com/bnema/zut/packages/provider"
27+ "github.com/bnema/zut/packages/provider/auth"
28+ "github.com/bnema/zut/packages/tui"
2929 )
3030
3131 // interactiveExtHooks is a tiny adapter that lets the extension
@@ -471,7 +471,7 @@ func prepareSessionResumeWithOptions(path string, current *core.Agent, currentPr
471471 }
472472
473473 // Old sessions may have either field absent. Only a complete stored
474- // selection is actionable; otherwise resume exactly as older zot did.
474+ // selection is actionable; otherwise resume exactly as older zut did.
475475 if storedProvider == "" || storedModel == "" {
476476 keepSession = true
477477 return candidate, nil
@@ -688,7 +688,7 @@ func fanoutAgentEvent(mgr *extensions.Manager, ev core.AgentEvent) {
688688 }
689689 }
690690
691-// Run is the top-level entrypoint for the zot binary.
691+// Run is the top-level entrypoint for the zut binary.
692692 func Run(rawArgs []string, version string) error {
693693 // Extension installs can invoke external build and clone processes.
694694 // Give those processes a cancellation context without changing signal
@@ -700,7 +700,7 @@ func Run(rawArgs []string, version string) error {
700700 }
701701 defer stopExtContext()
702702
703- // Subcommand router: `zot bot ...` is handled separately so the
703+ // Subcommand router: `zut bot ...` is handled separately so the
704704 // generic flag parser doesn't reject "bot" as a positional arg.
705705 if handled, err := runBotCommand(rawArgs, version); handled {
706706 return err
@@ -711,14 +711,14 @@ func Run(rawArgs []string, version string) error {
711711 if handled, err := runUpdateCommand(rawArgs, version); handled {
712712 return err
713713 }
714- if handled, err := runZotfileCommand(rawArgs, version); handled {
714+ if handled, err := runZutfileCommand(rawArgs, version); handled {
715715 return err
716716 }
717717 return runWithArgsRaw(rawArgs, version)
718718 }
719719
720720 func runWithArgsRaw(rawArgs []string, version string) error {
721- // `zot rpc` is shorthand for `zot --rpc` so third-party apps can
721+ // `zut rpc` is shorthand for `zut --rpc` so third-party apps can
722722 // spawn the binary with a clean argv. Strip the leading 'rpc'
723723 // token and let the rest flow through the normal arg parser.
724724 if len(rawArgs) > 0 && rawArgs[0] == "rpc" {
@@ -735,7 +735,7 @@ func runWithArgsRaw(rawArgs []string, version string) error {
735735 return nil
736736 }
737737 if args.Version {
738- fmt.Println("zot", version)
738+ fmt.Println("zut", version)
739739 return nil
740740 }
741741 // Model catalog: load any cached discovery data before we inspect
@@ -797,7 +797,7 @@ func (nonInteractiveExtHooks) ClearWidget(string, string)
797797 // wire tools into the resolved registry, and a cleanup closure to
798798 // defer. Mirrors the interactive-mode setup minus the TUI hooks.
799799 func setupNonInteractiveExtensions(ctx context.Context, args Args, r *Resolved, version string) (*extensions.Manager, func()) {
800- extMgr := extensions.New(ZotHome(), r.CWD, version, r.Provider, r.Model, nonInteractiveExtHooks{})
800+ extMgr := extensions.New(ZutHome(), r.CWD, version, r.Provider, r.Model, nonInteractiveExtHooks{})
801801 for _, e := range extMgr.LoadExplicit(ctx, args.Exts) {
802802 fmt.Fprintln(os.Stderr, "extension load:", e)
803803 }
@@ -925,7 +925,7 @@ func runPrintMode(ctx context.Context, args Args, version string) error {
925925 }
926926
927927 start := len(ag.Messages())
928- if err := runZotfileStartupPre(ctx, args.StartupPre, r.CWD, r.Sandbox, ag, nil, os.Stderr); err != nil {
928+ if err := runZutfileStartupPre(ctx, args.StartupPre, r.CWD, r.Sandbox, ag, nil, os.Stderr); err != nil {
929929 return err
930930 }
931931 if err := reloadResourcesAfterStartupPre(ctx, args, extMgr, r.Sandbox, ag); err != nil {
@@ -991,7 +991,7 @@ func runStreamMode(ctx context.Context, args Args, version string) error {
991991
992992 start := len(ag.Messages())
993993 preSink, finishPre := newStreamTextSink(os.Stdout)
994- if err := runZotfileStartupPre(ctx, args.StartupPre, r.CWD, r.Sandbox, ag, preSink, os.Stderr); err != nil {
994+ if err := runZutfileStartupPre(ctx, args.StartupPre, r.CWD, r.Sandbox, ag, preSink, os.Stderr); err != nil {
995995 finishPre()
996996 return err
997997 }
@@ -1098,7 +1098,7 @@ func runJSONMode(ctx context.Context, args Args, version string) error {
10981098 preSink := func(ev core.AgentEvent) {
10991099 _ = enc.Encode(modes.EventToJSON(ev))
11001100 }
1101- if err := runZotfileStartupPre(ctx, args.StartupPre, r.CWD, r.Sandbox, ag, preSink, os.Stderr); err != nil {
1101+ if err := runZutfileStartupPre(ctx, args.StartupPre, r.CWD, r.Sandbox, ag, preSink, os.Stderr); err != nil {
11021102 _ = enc.Encode(map[string]any{"type": "error", "message": err.Error()})
11031103 return err
11041104 }
@@ -1111,11 +1111,11 @@ func runJSONMode(ctx context.Context, args Args, version string) error {
11111111 return err
11121112 }
11131113
1114-// runZotfileStartupPre runs entry.pre before the main non-interactive prompt.
1114+// runZutfileStartupPre runs entry.pre before the main non-interactive prompt.
11151115 // "!command" values execute via BashTool; other values are sent as an agent turn.
11161116 // shellOut receives live shell chunks (typically os.Stderr); sink receives
11171117 // agent events for plain-text pre (stream mode wires a live text sink).
1118-func runZotfileStartupPre(ctx context.Context, pre, cwd string, sandbox *tools.Sandbox, ag *core.Agent, sink func(core.AgentEvent), shellOut io.Writer) error {
1118+func runZutfileStartupPre(ctx context.Context, pre, cwd string, sandbox *tools.Sandbox, ag *core.Agent, sink func(core.AgentEvent), shellOut io.Writer) error {
11191119 pre = strings.TrimSpace(pre)
11201120 if pre == "" {
11211121 return nil
@@ -1143,7 +1143,7 @@ func runZotfileStartupPre(ctx context.Context, pre, cwd string, sandbox *tools.S
11431143 bash := &tools.BashTool{CWD: cwd, Sandbox: sandbox}
11441144 res, err := bash.Execute(ctx, raw, progress)
11451145 if err != nil {
1146- return fmt.Errorf("zotfile entry.pre: %w", err)
1146+ return fmt.Errorf("zutfile entry.pre: %w", err)
11471147 }
11481148 if res.IsError {
11491149 var sb strings.Builder
@@ -1156,12 +1156,12 @@ func runZotfileStartupPre(ctx context.Context, pre, cwd string, sandbox *tools.S
11561156 if msg == "" {
11571157 msg = "command failed"
11581158 }
1159- return fmt.Errorf("zotfile entry.pre: %s", msg)
1159+ return fmt.Errorf("zutfile entry.pre: %s", msg)
11601160 }
11611161 return nil
11621162 }
11631163 if ag == nil {
1164- return fmt.Errorf("zotfile entry.pre requires an agent for non-shell prompts")
1164+ return fmt.Errorf("zutfile entry.pre requires an agent for non-shell prompts")
11651165 }
11661166 if sink == nil {
11671167 sink = func(core.AgentEvent) {}
@@ -1174,7 +1174,7 @@ var errInteractiveAgentChanged = errors.New("interactive agent changed during pr
11741174 // refreshAgentToolsAndPrompt re-resolves tools (including rediscovered
11751175 // skills and currently loaded extension tools) and updates the live
11761176 // agent's registry and system prompt. Used after /reload-ext and after
1177-// zotfile entry.pre installs new skills or extensions.
1177+// zutfile entry.pre installs new skills or extensions.
11781178 // mutateRegistry, if non-nil, can inject session-specific tools (e.g. subagent_spawn).
11791179 // interactive, when non-nil, serializes the final commit with agent replacement.
11801180 func refreshAgentToolsAndPrompt(args Args, sharedSandbox *tools.Sandbox, extToolAdapter ExtensionToolSource, ag *core.Agent, mutateRegistry func(core.Registry) core.Registry, interactive *modes.Interactive) error {
@@ -1289,7 +1289,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
12891289 // host after constructing it below so startup alerts can be buffered.
12901290 var iv *modes.Interactive
12911291 extHooks := &interactiveExtHooks{}
1292- extMgr := extensions.New(ZotHome(), r.CWD, version, r.Provider, r.Model, extHooks)
1292+ extMgr := extensions.New(ZutHome(), r.CWD, version, r.Provider, r.Model, extHooks)
12931293 var startupExtensionErrors []string
12941294 // --ext paths first so they win against installed extensions of
12951295 // the same name (loadOne's first-write-wins semantics).
@@ -1321,7 +1321,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
13211321
13221322 // Build the subagent supervisor BEFORE the agent so the auto-subagents
13231323 // tool can reference it during tool-registry construction. State
1324- // lives under ZotHome/subagents so per-agent meta/events survive
1324+ // lives under ZutHome/subagents so per-agent meta/events survive
13251325 // restarts; the user can hunt orphaned agents down with
13261326 // `git worktree list` if anything misbehaves.
13271327 //
@@ -1331,7 +1331,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
13311331 var subagentsMgr *subagents.Supervisor
13321332 subagentsMgr = subagents.New(subagents.Config{
13331333 Context: ctx,
1334- Root: filepath.Join(ZotHome(), "subagents"),
1334+ Root: filepath.Join(ZutHome(), "subagents"),
13351335 RepoRoot: r.CWD,
13361336 Provider: r.Provider,
13371337 FastMode: r.FastMode,
@@ -1375,7 +1375,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
13751375 // shows them as detached and the user can resume / remove them.
13761376 _, _ = subagentsMgr.Reload()
13771377 if args.PermissionSet != nil {
1378- // Packaged zotfile agents have an explicit capability ceiling. A
1378+ // Packaged zutfile agents have an explicit capability ceiling. A
13791379 // child process would otherwise rebuild a fresh unrestricted sandbox,
13801380 // so do not expose either slash or tool-based delegation from this
13811381 // restricted host until worker capability propagation exists.
@@ -1559,7 +1559,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
15591559 // rescue (a bad key, a typo'd base URL, or a corporate gateway that
15601560 // only the originally-picked provider needed). Re-resolving without
15611561 // them lets the rescue retry use env vars / auth.json / provider
1562- // defaults the way zot would have without the overrides.
1562+ // defaults the way zut would have without the overrides.
15631563 buildAgentForRescue := func(providerOverride, modelOverride string) (*core.Agent, string, string, error) {
15641564 next := args
15651565 next.APIKey = ""
@@ -1961,10 +1961,10 @@ func runInteractive(ctx context.Context, args Args, version string) error {
19611961 // Fresh session in the new cwd's bucket. We bypass
19621962 // openOrCreateSession's --continue / --resume branches
19631963 // because /cd's semantics are "start fresh here", matching
1964- // what relaunching `zot --cwd <path>` would do today.
1964+ // what relaunching `zut --cwd <path>` would do today.
19651965 var newSess *core.Session
19661966 if !args.NoSess {
1967- newRoot := agentSessionsRoot(ZotHome(), args)
1967+ newRoot := agentSessionsRoot(ZutHome(), args)
19681968 core.PruneEmptySessions(newRoot, absPath)
19691969 var serr error
19701970 newSess, serr = core.NewSession(newRoot, absPath, newProvider, newModel, version)
@@ -2004,7 +2004,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
20042004
20052005 // Push the new state into the running Interactive.
20062006 if iv != nil {
2007- startupPaths := instructionContextPaths(loadAgentsContext(absPath, ZotHome()))
2007+ startupPaths := instructionContextPaths(loadAgentsContext(absPath, ZutHome()))
20082008 iv.ApplyChangedCWDWithStartupContext(newAg, newProvider, newModel, absPath, startupPaths)
20092009 }
20102010
@@ -2020,7 +2020,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
20202020
20212021 devBuild := isDevVersion(version)
20222022 if devBuild {
2023- fmt.Fprintln(os.Stderr, "zot:", devVersionNotice)
2023+ fmt.Fprintln(os.Stderr, "zut:", devVersionNotice)
20242024 }
20252025 term := tui.NewProcTerm()
20262026
@@ -2033,7 +2033,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
20332033 updateCh = make(chan modes.UpdateInfo, 1)
20342034 go func() {
20352035 defer close(updateCh)
2036- src := <-CheckForUpdateAsync(ZotHome(), version)
2036+ src := <-CheckForUpdateAsync(ZutHome(), version)
20372037 updateCh <- modes.UpdateInfo{
20382038 Current: src.Current,
20392039 Latest: src.Latest,
@@ -2076,10 +2076,10 @@ func runInteractive(ctx context.Context, args Args, version string) error {
20762076 for idx, s := range initialCfg.QuickModelShortcuts {
20772077 quickModelShortcuts[idx] = modes.QuickModelShortcut{Provider: s.Provider, Model: s.Model}
20782078 }
2079- theme, _, themeErr := tui.DetectThemeWithCustom(ZotHome(), initialCfg.Theme, 80*time.Millisecond)
2079+ theme, _, themeErr := tui.DetectThemeWithCustom(ZutHome(), initialCfg.Theme, 80*time.Millisecond)
20802080 if themeErr != nil {
20812081 fmt.Fprintln(os.Stderr, "theme load:", themeErr)
2082- if initialCfg.Theme != "" && !tui.ThemeExists(ZotHome(), initialCfg.Theme) {
2082+ if initialCfg.Theme != "" && !tui.ThemeExists(ZutHome(), initialCfg.Theme) {
20832083 initialCfg.Theme = ""
20842084 _ = SaveConfig(initialCfg)
20852085 }
@@ -2095,7 +2095,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
20952095 subagentsMgr.SetActiveSession(sess.ID)
20962096 }
20972097 // Best-effort shutdown on interactive exit: stop all running
2098- // agents so they don't outlive their parent zot.
2098+ // agents so they don't outlive their parent zut.
20992099 if subagentsMgr != nil {
21002100 defer subagentsMgr.StopAll()
21012101 }
@@ -2154,8 +2154,8 @@ func runInteractive(ctx context.Context, args Args, version string) error {
21542154 StartupExtensionErrors: startupExtensionErrors,
21552155 StartupSkillNames: startupSkillNames(startupSkills),
21562156 ShowInstructionsAtStartup: initialCfg.ShowInstructionsAtStartup,
2157- ZotHome: ZotHome(),
2158- SessionsRoot: agentSessionsRoot(ZotHome(), args),
2157+ ZutHome: ZutHome(),
2158+ SessionsRoot: agentSessionsRoot(ZutHome(), args),
21592159 Version: version,
21602160 UpdateInfoChan: updateCh,
21612161 Sandbox: sharedSandbox,
@@ -2281,7 +2281,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
22812281 // model still sees them through the system-prompt
22822282 // manifest and the skill tool.
22832283 userHome, _ := os.UserHomeDir()
2284- list, _ := skills.Discover(ZotHome(), r.CWD, userHome, args.WithSkills)
2284+ list, _ := skills.Discover(ZutHome(), r.CWD, userHome, args.WithSkills)
22852285 list = mergeExtensionSkills(skills.NewTool(list), extMgr.Skills())
22862286 return skills.VisibleSkills(list)
22872287 },
@@ -2345,7 +2345,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
23452345 })
23462346 }
23472347
2348- // Signal-driven flush: a SIGTERM / SIGHUP to the zot process
2348+ // Signal-driven flush: a SIGTERM / SIGHUP to the zut process
23492349 // (closed terminal window, system shutdown, kill) used to lose
23502350 // the entire in-memory transcript because the deferred post-Run
23512351 // flush below never ran. Per-message persistence above covers
@@ -2419,10 +2419,10 @@ func openOrCreateSession(args Args, r Resolved, ag *core.Agent, version string)
24192419 if args.NoSess {
24202420 return nil, nil
24212421 }
2422- // Sweep meta-only files left over from older zot versions (and from
2422+ // Sweep meta-only files left over from older zut versions (and from
24232423 // any session that crashed before its first AppendMessage). Cheap;
24242424 // reads the first few bytes of each file in the cwd's session dir.
2425- sessionsRoot := agentSessionsRoot(ZotHome(), args)
2425+ sessionsRoot := agentSessionsRoot(ZutHome(), args)
24262426 core.PruneEmptySessions(sessionsRoot, args.CWD)
24272427 var (
24282428 s *core.Session
M packages/agent/cli_alert_test.go +2 -2
@@ -3,8 +3,8 @@ package agent
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/agent/extproto"
7- "github.com/patriceckhart/zot/packages/agent/modes"
6+ "github.com/bnema/zut/packages/agent/extproto"
7+ "github.com/bnema/zut/packages/agent/modes"
88 )
99
1010 func TestInteractiveExtHooksBufferStartupChrome(t *testing.T) {
M packages/agent/cli_session_test.go +4 -4
@@ -9,9 +9,9 @@ import (
99 "testing"
1010 "time"
1111
12- "github.com/patriceckhart/zot/packages/agent/modes"
13- "github.com/patriceckhart/zot/packages/core"
14- "github.com/patriceckhart/zot/packages/provider"
12+ "github.com/bnema/zut/packages/agent/modes"
13+ "github.com/bnema/zut/packages/core"
14+ "github.com/bnema/zut/packages/provider"
1515 )
1616
1717 func TestTrimMessagesForResumeCarriesDeferredToolActivation(t *testing.T) {
@@ -97,7 +97,7 @@ func TestLiveInteractiveAgentFallsBackBeforeInteractiveConstruction(t *testing.T
9797 }
9898
9999 func TestPersistModelCallbackDoesNotReenterSessionTransition(t *testing.T) {
100- t.Setenv("ZOT_HOME", t.TempDir())
100+ t.Setenv("ZUT_HOME", t.TempDir())
101101 sess, err := core.NewSession(t.TempDir(), t.TempDir(), "old-provider", "old-model", "test")
102102 if err != nil {
103103 t.Fatal(err)
M packages/agent/config.go +25 -25
@@ -12,8 +12,8 @@ import (
1212 "strings"
1313 "time"
1414
15- providerpkg "github.com/patriceckhart/zot/packages/provider"
16- "github.com/patriceckhart/zot/packages/provider/auth"
15+ providerpkg "github.com/bnema/zut/packages/provider"
16+ "github.com/bnema/zut/packages/provider/auth"
1717 )
1818
1919 // QuickModelShortcut is one configured keyboard shortcut slot.
@@ -57,13 +57,13 @@ type Config struct {
5757 // ToolRender selects how tool calls are drawn in interactive mode.
5858 // "box" (default, or empty) wraps each call in a bordered panel;
5959 // "flat" drops the frame for a quiet header line plus indented,
60- // frameless output. The ZOT_FLAT_TOOLS env var overrides this when
60+ // frameless output. The ZUT_FLAT_TOOLS env var overrides this when
6161 // set ("1"/"true" forces flat, "0"/"false" forces box).
6262 ToolRender string `json:"tool_render,omitempty"`
6363
6464 // CompactInput renders sent user messages as a single quiet gutter
6565 // line instead of a padded, background-tinted bubble. nil/false
66- // (the default) keeps the bubble. The ZOT_COMPACT_INPUT env var
66+ // (the default) keeps the bubble. The ZUT_COMPACT_INPUT env var
6767 // overrides this when set.
6868 CompactInput *bool `json:"compact_input,omitempty"`
6969
@@ -71,7 +71,7 @@ type Config struct {
7171 // Ctrl+1..9. Cmd+1..9 may also work on terminals that forward Super.
7272 QuickModelShortcuts []QuickModelShortcut `json:"quick_model_shortcuts,omitempty"`
7373
74- // InlineImagesEnabled controls whether zot draws screenshots inline
74+ // InlineImagesEnabled controls whether zut draws screenshots inline
7575 // when the terminal supports an image protocol. nil/missing means
7676 // auto (enabled when supported); false disables; true forces the
7777 // detected protocol when available.
@@ -174,43 +174,43 @@ type Config struct {
174174 LastChangelogShown string `json:"last_changelog_shown,omitempty"`
175175 }
176176
177-// ZotHome returns $ZOT_HOME or the OS-default data dir.
177+// ZutHome returns $ZUT_HOME or the OS-default data dir.
178178 //
179-// All zot state (config.json, auth.json, sessions/, logs/) lives under
179+// All zut state (config.json, auth.json, sessions/, logs/) lives under
180180 // this directory.
181-func ZotHome() string {
182- if v := os.Getenv("ZOT_HOME"); v != "" {
181+func ZutHome() string {
182+ if v := os.Getenv("ZUT_HOME"); v != "" {
183183 return v
184184 }
185185 if v := os.Getenv("XDG_STATE_HOME"); v != "" {
186- return filepath.Join(v, "zot")
186+ return filepath.Join(v, "zut")
187187 }
188188 switch runtime.GOOS {
189189 case "darwin":
190190 if home, err := os.UserHomeDir(); err == nil {
191- return filepath.Join(home, "Library", "Application Support", "zot")
191+ return filepath.Join(home, "Library", "Application Support", "zut")
192192 }
193193 case "windows":
194194 if v := os.Getenv("LOCALAPPDATA"); v != "" {
195- return filepath.Join(v, "zot")
195+ return filepath.Join(v, "zut")
196196 }
197197 }
198198 if home, err := os.UserHomeDir(); err == nil {
199- return filepath.Join(home, ".local", "state", "zot")
199+ return filepath.Join(home, ".local", "state", "zut")
200200 }
201- return ".zot"
201+ return ".zut"
202202 }
203203
204204 // ConfigPath returns the path to config.json.
205-func ConfigPath() string { return filepath.Join(ZotHome(), "config.json") }
205+func ConfigPath() string { return filepath.Join(ZutHome(), "config.json") }
206206
207207 // FlatToolRender reports whether tool calls should render flat (no
208-// bordered panel). The ZOT_FLAT_TOOLS env var takes precedence over
208+// bordered panel). The ZUT_FLAT_TOOLS env var takes precedence over
209209 // the config when set: "1"/"true"/"yes"/"on" force flat, "0"/"false"/
210210 // "no"/"off" force box. Otherwise the config's tool_render is
211211 // consulted; "flat" is flat, anything else (including empty) is box.
212212 func (c Config) FlatToolRender() bool {
213- if v := strings.TrimSpace(strings.ToLower(os.Getenv("ZOT_FLAT_TOOLS"))); v != "" {
213+ if v := strings.TrimSpace(strings.ToLower(os.Getenv("ZUT_FLAT_TOOLS"))); v != "" {
214214 switch v {
215215 case "1", "true", "yes", "on", "flat":
216216 return true
@@ -223,12 +223,12 @@ func (c Config) FlatToolRender() bool {
223223
224224 // CompactUserInput reports whether sent user messages should render as
225225 // a single quiet gutter line instead of a padded, background-tinted
226-// bubble. The ZOT_COMPACT_INPUT env var takes precedence over the
226+// bubble. The ZUT_COMPACT_INPUT env var takes precedence over the
227227 // config when set: "1"/"true"/"yes"/"on" force compact, "0"/"false"/
228228 // "no"/"off" force the bubble. Otherwise the config's compact_input
229229 // is consulted (nil/false means the bubble).
230230 func (c Config) CompactUserInput() bool {
231- if v := strings.TrimSpace(strings.ToLower(os.Getenv("ZOT_COMPACT_INPUT"))); v != "" {
231+ if v := strings.TrimSpace(strings.ToLower(os.Getenv("ZUT_COMPACT_INPUT"))); v != "" {
232232 switch v {
233233 case "1", "true", "yes", "on", "compact":
234234 return true
@@ -269,19 +269,19 @@ func (c Config) LSPDiagnosticsOnEditEnabled(subagent bool) bool {
269269 }
270270
271271 // AuthPath returns the path to auth.json.
272-func AuthPath() string { return filepath.Join(ZotHome(), "auth.json") }
272+func AuthPath() string { return filepath.Join(ZutHome(), "auth.json") }
273273
274274 // KimiCLIFallbackDisabledPath returns a sentinel that disables falling
275-// back to the official Kimi Code CLI token after `zot /logout kimi`.
275+// back to the official Kimi Code CLI token after `zut /logout kimi`.
276276 func KimiCLIFallbackDisabledPath() string {
277- return filepath.Join(ZotHome(), "kimi-cli-fallback-disabled")
277+ return filepath.Join(ZutHome(), "kimi-cli-fallback-disabled")
278278 }
279279
280280 // SessionsPath returns the directory holding session files.
281-func SessionsPath() string { return filepath.Join(ZotHome(), "sessions") }
281+func SessionsPath() string { return filepath.Join(ZutHome(), "sessions") }
282282
283283 // LogsPath returns the directory holding log files.
284-func LogsPath() string { return filepath.Join(ZotHome(), "logs") }
284+func LogsPath() string { return filepath.Join(ZutHome(), "logs") }
285285
286286 // LoadConfig reads the config file, returning defaults if missing.
287287 func LoadConfig() (Config, error) {
@@ -301,7 +301,7 @@ func LoadConfig() (Config, error) {
301301
302302 // SaveConfig writes the config file, creating parent dirs.
303303 func SaveConfig(c Config) error {
304- if err := os.MkdirAll(ZotHome(), 0o755); err != nil {
304+ if err := os.MkdirAll(ZutHome(), 0o755); err != nil {
305305 return err
306306 }
307307 b, err := json.MarshalIndent(c, "", " ")
M packages/agent/config_command_test.go +4 -4
@@ -9,11 +9,11 @@ import (
99 "strings"
1010 "testing"
1111
12- "github.com/patriceckhart/zot/packages/provider/auth"
12+ "github.com/bnema/zut/packages/provider/auth"
1313 )
1414
1515 func TestAgentAPIKeyCommandHelperProcess(t *testing.T) {
16- if os.Getenv("ZOT_AGENT_API_KEY_COMMAND_HELPER") != "1" {
16+ if os.Getenv("ZUT_AGENT_API_KEY_COMMAND_HELPER") != "1" {
1717 return
1818 }
1919 args := os.Args
@@ -32,9 +32,9 @@ func TestAgentAPIKeyCommandHelperProcess(t *testing.T) {
3232
3333 func TestCommandCredentialIsLazy(t *testing.T) {
3434 home := t.TempDir()
35- t.Setenv("ZOT_HOME", home)
35+ t.Setenv("ZUT_HOME", home)
3636 t.Setenv("LAZY_PROVIDER_API_KEY", "")
37- t.Setenv("ZOT_AGENT_API_KEY_COMMAND_HELPER", "1")
37+ t.Setenv("ZUT_AGENT_API_KEY_COMMAND_HELPER", "1")
3838 marker := filepath.Join(home, "command-ran")
3939 credentials := auth.Credentials{AdditionalAPIKeyCreds: map[string]auth.ProviderCreds{
4040 "lazy-provider": {
M packages/agent/config_gcp_test.go +6 -6
@@ -5,7 +5,7 @@ import (
55 "path/filepath"
66 "testing"
77
8- providerauth "github.com/patriceckhart/zot/packages/provider/auth"
8+ providerauth "github.com/bnema/zut/packages/provider/auth"
99 )
1010
1111 func isolateGoogleVertexADC(t *testing.T) string {
@@ -24,7 +24,7 @@ func isolateGoogleVertexADC(t *testing.T) string {
2424 // takes top priority for the google-vertex provider and is returned with
2525 // method "apikey".
2626 func TestResolveCredentialGoogleVertexAPIKey(t *testing.T) {
27- t.Setenv("ZOT_HOME", t.TempDir())
27+ t.Setenv("ZUT_HOME", t.TempDir())
2828 isolateGoogleVertexADC(t)
2929 t.Setenv("GOOGLE_CLOUD_API_KEY", "gcp-key-123")
3030 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
@@ -46,7 +46,7 @@ func TestResolveCredentialGoogleVertexAPIKey(t *testing.T) {
4646 // resolution returns the "<adc>" sentinel with method "apikey", matching
4747 // the real NewVertex client's ADC-based auth flow.
4848 func TestResolveCredentialGoogleVertexApplicationCredentialsEnv(t *testing.T) {
49- t.Setenv("ZOT_HOME", t.TempDir())
49+ t.Setenv("ZUT_HOME", t.TempDir())
5050 isolateGoogleVertexADC(t)
5151 t.Setenv("GOOGLE_CLOUD_API_KEY", "")
5252 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/service-account.json")
@@ -67,7 +67,7 @@ func TestResolveCredentialGoogleVertexApplicationCredentialsEnv(t *testing.T) {
6767 // env vars, the presence of the platform-specific default gcloud ADC file is
6868 // detected and resolves to the "<adc>" sentinel.
6969 func TestResolveCredentialGoogleVertexDefaultADCFile(t *testing.T) {
70- t.Setenv("ZOT_HOME", t.TempDir())
70+ t.Setenv("ZUT_HOME", t.TempDir())
7171 adcPath := isolateGoogleVertexADC(t)
7272 t.Setenv("GOOGLE_CLOUD_API_KEY", "")
7373 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
@@ -95,7 +95,7 @@ func TestResolveCredentialGoogleVertexDefaultADCFile(t *testing.T) {
9595 // wins over GOOGLE_APPLICATION_CREDENTIALS and the default ADC file when
9696 // multiple credential sources are present simultaneously.
9797 func TestResolveCredentialGoogleVertexPrecedence(t *testing.T) {
98- t.Setenv("ZOT_HOME", t.TempDir())
98+ t.Setenv("ZUT_HOME", t.TempDir())
9999 adcPath := isolateGoogleVertexADC(t)
100100 t.Setenv("GOOGLE_CLOUD_API_KEY", "explicit-key")
101101 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/sa.json")
@@ -124,7 +124,7 @@ func TestResolveCredentialGoogleVertexPrecedence(t *testing.T) {
124124 // auth.json entry), resolution fails with an error rather than silently
125125 // returning an empty credential.
126126 func TestResolveCredentialGoogleVertexNoCredentials(t *testing.T) {
127- t.Setenv("ZOT_HOME", t.TempDir())
127+ t.Setenv("ZUT_HOME", t.TempDir())
128128 isolateGoogleVertexADC(t)
129129 t.Setenv("GOOGLE_CLOUD_API_KEY", "")
130130 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
M packages/agent/config_home_test.go +8 -8
@@ -5,20 +5,20 @@ import (
55 "testing"
66 )
77
8-func TestZotHomePrefersExplicitHomeOverXDGStateHome(t *testing.T) {
9- t.Setenv("ZOT_HOME", filepath.Join("explicit", "zot"))
8+func TestZutHomePrefersExplicitHomeOverXDGStateHome(t *testing.T) {
9+ t.Setenv("ZUT_HOME", filepath.Join("explicit", "zut"))
1010 t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))
1111
12- if got, want := ZotHome(), filepath.Join("explicit", "zot"); got != want {
13- t.Fatalf("ZotHome() = %q, want %q", got, want)
12+ if got, want := ZutHome(), filepath.Join("explicit", "zut"); got != want {
13+ t.Fatalf("ZutHome() = %q, want %q", got, want)
1414 }
1515 }
1616
17-func TestZotHomeUsesXDGStateHome(t *testing.T) {
18- t.Setenv("ZOT_HOME", "")
17+func TestZutHomeUsesXDGStateHome(t *testing.T) {
18+ t.Setenv("ZUT_HOME", "")
1919 t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))
2020
21- if got, want := ZotHome(), filepath.Join("xdg", "state", "zot"); got != want {
22- t.Fatalf("ZotHome() = %q, want %q", got, want)
21+ if got, want := ZutHome(), filepath.Join("xdg", "state", "zut"); got != want {
22+ t.Fatalf("ZutHome() = %q, want %q", got, want)
2323 }
2424 }
M packages/agent/confirmation_events.go +2 -2
@@ -1,8 +1,8 @@
11 package agent
22
33 import (
4- "github.com/patriceckhart/zot/packages/agent/extproto"
5- "github.com/patriceckhart/zot/packages/core"
4+ "github.com/bnema/zut/packages/agent/extproto"
5+ "github.com/bnema/zut/packages/core"
66 )
77
88 // confirmationEventConfirmer decorates an interactive confirmer with an
M packages/agent/confirmation_events_test.go +2 -2
@@ -4,8 +4,8 @@ import (
44 "reflect"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/agent/extproto"
8- "github.com/patriceckhart/zot/packages/core"
7+ "github.com/bnema/zut/packages/agent/extproto"
8+ "github.com/bnema/zut/packages/core"
99 )
1010
1111 type rememberingDetailedConfirmer struct {
M packages/agent/ext/ext.go +33 -29
@@ -1,6 +1,6 @@
1-// Package ext is the Go SDK for writing zot extensions.
1+// Package ext is the Go SDK for writing zut extensions.
22 //
3-// An extension is a subprocess that talks to zot over its stdin/stdout
3+// An extension is a subprocess that talks to zut over its stdin/stdout
44 // in newline-delimited JSON. This package wraps the wire format so
55 // extension authors can write straightforward Go without reimplementing
66 // the protocol.
@@ -9,7 +9,7 @@
99 //
1010 // package main
1111 //
12-// import "github.com/patriceckhart/zot/packages/agent/ext"
12+// import "github.com/bnema/zut/packages/agent/ext"
1313 //
1414 // func main() {
1515 // ext := ext.New("hello", "1.0.0")
@@ -20,10 +20,10 @@
2020 // }
2121 //
2222 // Build it, drop the binary + an extension.json next to it under
23-// `$ZOT_HOME/extensions/hello/`, and zot picks it up on next launch.
23+// `$ZUT_HOME/extensions/hello/`, and zut picks it up on next launch.
2424 //
2525 // Use OnHello when registration or configuration depends on host metadata
26-// such as HostInfo.CWD, Provider, Model, ZotVersion, ExtensionDir, or DataDir.
26+// such as HostInfo.CWD, Provider, Model, ZutVersion, ExtensionDir, or DataDir.
2727 // Run sends hello, waits for hello_ack, runs OnHello, announces registrations,
2828 // then sends ready.
2929 //
@@ -40,7 +40,7 @@ import (
4040 "os"
4141 "sync"
4242
43- "github.com/patriceckhart/zot/packages/agent/extproto"
43+ "github.com/bnema/zut/packages/agent/extproto"
4444 )
4545
4646 func base64Encode(b []byte) string { return base64.StdEncoding.EncodeToString(b) }
@@ -48,13 +48,13 @@ func base64Encode(b []byte) string { return base64.StdEncoding.EncodeToString(b)
4848 // CommandHandler is invoked when the user runs the extension's
4949 // registered slash command. args is everything the user typed after
5050 // the command name (already trimmed). Return a Response describing
51-// what zot should do next.
51+// what zut should do next.
5252 type CommandHandler func(args string) Response
5353
5454 // ToolHandler is invoked when the LLM calls a tool the extension
5555 // registered. args is the raw JSON object the model produced; the
5656 // handler is responsible for parsing/validating it. Return a
57-// ToolResult describing what zot should send back to the model.
57+// ToolResult describing what zut should send back to the model.
5858 type ToolHandler func(args json.RawMessage) ToolResult
5959
6060 // EventHandler is called for each lifecycle event the extension
@@ -63,7 +63,7 @@ type ToolHandler func(args json.RawMessage) ToolResult
6363 // worker.
6464 type EventHandler func(ev Event)
6565
66-// Event is a lifecycle notification from zot. The fields populated
66+// Event is a lifecycle notification from zut. The fields populated
6767 // depend on Name (the host's event_name string):
6868 //
6969 // session_start : Session
@@ -221,7 +221,7 @@ func TextErrorResult(s string) ToolResult {
221221 return ToolResult{Content: []ToolContent{Text(s)}, IsError: true}
222222 }
223223
224-// Response tells zot how to react to a command invocation. Construct
224+// Response tells zut how to react to a command invocation. Construct
225225 // one with Prompt(), Insert(), Display(), or Noop().
226226 type Panel struct {
227227 ID string
@@ -254,7 +254,7 @@ func Insert(text string) Response { return Response{Action: "insert", Insert: te
254254 func Display(text string) Response { return Response{Action: "display", Display: text} }
255255
256256 // OpenPanel returns a Response that opens an interactive extension-owned
257-// panel inside zot.
257+// panel inside zut.
258258 func OpenPanel(id, title string, lines []string, footer string) Response {
259259 return Response{Action: "open_panel", OpenPanel: &Panel{ID: id, Title: title, Lines: lines, Footer: footer}}
260260 }
@@ -269,7 +269,7 @@ func Errorf(format string, args ...any) Response {
269269 return Response{Action: "noop", Error: fmt.Sprintf(format, args...)}
270270 }
271271
272-// Extension is one zot extension. Construct with New, register
272+// Extension is one zut extension. Construct with New, register
273273 // commands, then call Run.
274274 type Extension struct {
275275 name string
@@ -315,11 +315,11 @@ type toolDef struct {
315315 deferred bool
316316 }
317317
318-// HostInfo is what the host (zot) tells us in HelloAck. Useful for
318+// HostInfo is what the host (zut) tells us in HelloAck. Useful for
319319 // extensions that want to behave differently per provider.
320320 type HostInfo struct {
321321 ProtocolVersion int
322- ZotVersion string
322+ ZutVersion string
323323 Provider string
324324 Model string
325325 CWD string
@@ -350,9 +350,9 @@ func New(name, version string) *Extension {
350350 // Returns the zero value if Run hasn't started yet.
351351 func (e *Extension) Host() HostInfo { return e.host }
352352
353-// OnHello registers a callback that runs after zot acknowledges the
353+// OnHello registers a callback that runs after zut acknowledges the
354354 // hello frame and before commands/tools are announced. Use it when
355-// registrations depend on host metadata such as CWD or ZotHome-derived
355+// registrations depend on host metadata such as CWD or ZutHome-derived
356356 // paths. The callback may call Command, Tool, On, and interceptor
357357 // registration methods.
358358 func (e *Extension) OnHello(fn func(HostInfo)) {
@@ -361,8 +361,8 @@ func (e *Extension) OnHello(fn func(HostInfo)) {
361361 e.mu.Unlock()
362362 }
363363
364-// Logf writes a line to the extension's stderr, which zot captures to
365-// $ZOT_HOME/logs/ext-<name>.log. Use this for debug output: anything
364+// Logf writes a line to the extension's stderr, which zut captures to
365+// $ZUT_HOME/logs/ext-<name>.log. Use this for debug output: anything
366366 // you print to stdout would corrupt the JSON wire protocol.
367367 func (e *Extension) Logf(format string, args ...any) {
368368 fmt.Fprintf(e.stderr, "["+e.name+"] "+format+"\n", args...)
@@ -396,13 +396,13 @@ func (e *Extension) RenderPanel(panelID, title string, lines []string, footer st
396396 _ = e.send(extproto.PanelRenderFromExt{Type: "panel_render", PanelID: panelID, Title: title, Lines: lines, Footer: footer})
397397 }
398398
399-// ClosePanel tells zot to close panelID.
399+// ClosePanel tells zut to close panelID.
400400 func (e *Extension) ClosePanel(panelID string) {
401401 _ = e.send(extproto.PanelCloseFromExt{Type: "panel_close", PanelID: panelID})
402402 }
403403
404404 // Command registers a slash-command handler. Call this BEFORE Run().
405-// Once Run is going, when the user runs /name in zot, fn is invoked
405+// Once Run is going, when the user runs /name in zut, fn is invoked
406406 // with the remaining args.
407407 //
408408 // Naming conflicts with built-in commands (e.g. /help) are silently
@@ -417,7 +417,7 @@ func (e *Extension) Command(name, description string, fn CommandHandler) {
417417
418418 // Tool registers an LLM-callable tool. schema is a JSON Schema
419419 // object describing the tool's args (the same shape Anthropic /
420-// OpenAI accept). Call this BEFORE Run(); zot folds extension tools
420+// OpenAI accept). Call this BEFORE Run(); zut folds extension tools
421421 // into the agent's registry once the extension's ready frame fires.
422422 //
423423 // Naming conflicts with built-in tools (read, write, edit, bash, lsp,
@@ -501,7 +501,7 @@ func (e *Extension) InterceptAssistantMessage(fn AssistantMessageHandler) {
501501 e.mu.Unlock()
502502 }
503503
504-// Notify pushes an info-level status note into zot's chat without
504+// Notify pushes an info-level status note into zut's chat without
505505 // requiring a slash command from the user.
506506 func (e *Extension) Notify(level, message string) {
507507 _ = e.send(extproto.NotifyFromExt{
@@ -549,20 +549,21 @@ func (e *Extension) Alert(alert AlertRequest) {
549549 })
550550 }
551551
552-// Run starts the protocol loop. Blocks until stdin closes (zot has
552+// Run starts the protocol loop. Blocks until stdin closes (zut has
553553 // shut us down). Returns the first fatal error, or nil on clean exit.
554554 func (e *Extension) Run() error {
555555 scanner := bufio.NewScanner(e.in)
556556 scanner.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)
557557
558- // Send hello first. zot answers with hello_ack immediately; read it
558+ // Send hello first. zut answers with hello_ack immediately; read it
559559 // synchronously so extensions can use HostInfo while registering
560560 // commands/tools before the ready frame.
561561 if err := e.send(extproto.HelloFromExt{
562- Type: "hello",
563- Name: e.name,
564- Version: e.version,
565- Capabilities: e.caps,
562+ Type: "hello",
563+ ProtocolVersion: extproto.ProtocolVersion,
564+ Name: e.name,
565+ Version: e.version,
566+ Capabilities: e.caps,
566567 }); err != nil {
567568 return err
568569 }
@@ -576,9 +577,12 @@ func (e *Extension) Run() error {
576577 if ack.Type != "hello_ack" {
577578 return fmt.Errorf("first host frame must be hello_ack (got %q)", ack.Type)
578579 }
580+ if ack.ProtocolVersion != extproto.ProtocolVersion {
581+ return fmt.Errorf("unsupported host protocol version %d (want %d)", ack.ProtocolVersion, extproto.ProtocolVersion)
582+ }
579583 e.host = HostInfo{
580584 ProtocolVersion: ack.ProtocolVersion,
581- ZotVersion: ack.ZotVersion,
585+ ZutVersion: ack.ZutVersion,
582586 Provider: ack.Provider,
583587 Model: ack.Model,
584588 CWD: ack.CWD,
M packages/agent/ext/ext_test.go +31 -3
@@ -9,7 +9,7 @@ import (
99 "testing"
1010 "time"
1111
12- "github.com/patriceckhart/zot/packages/agent/extproto"
12+ "github.com/bnema/zut/packages/agent/extproto"
1313 )
1414
1515 // ---------- test harness ----------
@@ -140,10 +140,17 @@ func (h *extHarness) handshake(t *testing.T) {
140140 if f.hdr.Type != "hello" {
141141 t.Fatalf("expected hello, got %q", f.hdr.Type)
142142 }
143+ var hello extproto.HelloFromExt
144+ if err := json.Unmarshal(f.raw, &hello); err != nil {
145+ t.Fatalf("unmarshal hello: %v", err)
146+ }
147+ if hello.ProtocolVersion != extproto.ProtocolVersion {
148+ t.Fatalf("hello protocol version = %d, want %d", hello.ProtocolVersion, extproto.ProtocolVersion)
149+ }
143150 h.sendToExt(t, extproto.HelloAckFromHost{
144151 Type: "hello_ack",
145152 ProtocolVersion: extproto.ProtocolVersion,
146- ZotVersion: "0.0.0-test",
153+ ZutVersion: "0.0.0-test",
147154 Provider: "anthropic",
148155 Model: "claude-test",
149156 })
@@ -157,6 +164,27 @@ func (h *extHarness) handshake(t *testing.T) {
157164
158165 // ---------- tests ----------
159166
167+func TestRunRejectsUnsupportedHostProtocolVersion(t *testing.T) {
168+ h := newHarness("versioned-ext")
169+ runDone := make(chan error, 1)
170+ go func() { runDone <- h.ext.Run() }()
171+ t.Cleanup(func() { _ = h.hostW.Close() })
172+
173+ if frame := h.next(t); frame.hdr.Type != "hello" {
174+ t.Fatalf("expected hello, got %q", frame.hdr.Type)
175+ }
176+ h.sendToExt(t, extproto.HelloAckFromHost{Type: "hello_ack", ProtocolVersion: extproto.ProtocolVersion - 1})
177+
178+ select {
179+ case err := <-runDone:
180+ if err == nil || !strings.Contains(err.Error(), "unsupported host protocol version") {
181+ t.Fatalf("Run error = %v", err)
182+ }
183+ case <-time.After(2 * time.Second):
184+ t.Fatal("Run did not reject an unsupported host protocol version")
185+ }
186+}
187+
160188 // TestOnHelloCanRegisterUsingHostInfo checks that extensions can use
161189 // host metadata before announcing their initial registrations.
162190 func TestOnHelloCanRegisterUsingHostInfo(t *testing.T) {
@@ -177,7 +205,7 @@ func TestOnHelloCanRegisterUsingHostInfo(t *testing.T) {
177205 h.sendToExt(t, extproto.HelloAckFromHost{
178206 Type: "hello_ack",
179207 ProtocolVersion: extproto.ProtocolVersion,
180- ZotVersion: "0.0.0-test",
208+ ZutVersion: "0.0.0-test",
181209 Provider: "anthropic",
182210 Model: "claude-test",
183211 CWD: "/tmp/project",
M packages/agent/extcmd.go +38 -38
@@ -14,12 +14,12 @@ import (
1414 "strings"
1515 "time"
1616
17- "github.com/patriceckhart/zot/packages/agent/extensions"
18- "github.com/patriceckhart/zot/packages/agent/extproto"
19- "github.com/patriceckhart/zot/packages/ignore"
17+ "github.com/bnema/zut/packages/agent/extensions"
18+ "github.com/bnema/zut/packages/agent/extproto"
19+ "github.com/bnema/zut/packages/ignore"
2020 )
2121
22-// runExtCommand dispatches `zot ext ...` subcommands. Returns
22+// runExtCommand dispatches `zut ext ...` subcommands. Returns
2323 // (handled=true, err) if rawArgs starts with "ext"; otherwise
2424 // (handled=false, nil) so the main router falls through to the
2525 // regular flag parser.
@@ -56,22 +56,22 @@ func runExtCommand(ctx context.Context, rawArgs []string, version string) (handl
5656 }
5757
5858 func printExtHelp() {
59- fmt.Fprintln(os.Stderr, `zot ext — manage extensions
59+ fmt.Fprintln(os.Stderr, `zut ext — manage extensions
6060
6161 usage:
62- zot ext list list installed extensions and their state
63- zot ext doctor diagnose installed extensions
64- zot ext logs <name> [-f] cat / tail an extension's stderr log
65- zot ext enable <name> re-enable a disabled extension
66- zot ext disable <name> disable without removing
67- zot ext remove <name> delete an extension directory
68- zot ext install [--build=go] <path|git-url>
62+ zut ext list list installed extensions and their state
63+ zut ext doctor diagnose installed extensions
64+ zut ext logs <name> [-f] cat / tail an extension's stderr log
65+ zut ext enable <name> re-enable a disabled extension
66+ zut ext disable <name> disable without removing
67+ zut ext remove <name> delete an extension directory
68+ zut ext install [--build=go] <path|git-url>
6969 copy / clone and validate an extension
7070 --build=go explicitly builds a local Go extension
7171
7272 extensions live under:
73- $ZOT_HOME/extensions/<name>/extension.json (global)
74- ./.zot/extensions/<name>/extension.json (project-local)`)
73+ $ZUT_HOME/extensions/<name>/extension.json (global)
74+ ./.zut/extensions/<name>/extension.json (project-local)`)
7575 }
7676
7777 // extList walks both the global and project-local extension dirs and
@@ -122,7 +122,7 @@ func extList() error {
122122 }
123123 if len(rows) == 0 {
124124 fmt.Fprintln(os.Stderr, "no extensions installed")
125- fmt.Fprintln(os.Stderr, "see docs/extensions.md to write your own, or `zot ext install <path|url>`")
125+ fmt.Fprintln(os.Stderr, "see docs/extensions.md to write your own, or `zut ext install <path|url>`")
126126 return nil
127127 }
128128 fmt.Printf("%-12s %-20s %-10s %-8s %-10s %s\n", "scope", "name", "version", "enabled", "language", "dir")
@@ -169,14 +169,14 @@ func extDoctor(version string) error {
169169 rows := scanExtDoctorStatic()
170170 if len(rows) == 0 {
171171 fmt.Fprintln(os.Stdout, "no extensions installed")
172- fmt.Fprintln(os.Stdout, "see docs/extensions.md to write your own, or `zot ext install <path|url>`")
172+ fmt.Fprintln(os.Stdout, "see docs/extensions.md to write your own, or `zut ext install <path|url>`")
173173 return nil
174174 }
175175
176176 cwd, _ := os.Getwd()
177177 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
178178 defer cancel()
179- mgr := extensions.New(ZotHome(), cwd, version, "", "", extDoctorHooks{})
179+ mgr := extensions.New(ZutHome(), cwd, version, "", "", extDoctorHooks{})
180180 errs := mgr.Discover(ctx)
181181 mgr.WaitForReady(3 * time.Second)
182182 diags := mgr.Diagnostics()
@@ -187,7 +187,7 @@ func extDoctor(version string) error {
187187 diagByDir[d.Dir] = d
188188 }
189189
190- fmt.Fprintln(os.Stdout, "zot extension doctor")
190+ fmt.Fprintln(os.Stdout, "zut extension doctor")
191191 fmt.Fprintln(os.Stdout)
192192 for _, row := range rows {
193193 printExtDoctorRow(os.Stdout, row, diagByDir[row.Dir])
@@ -208,9 +208,9 @@ func scanExtDoctorStatic() []extDoctorStaticRow {
208208 }
209209 var dirs []scanDir
210210 if cwd, err := os.Getwd(); err == nil {
211- dirs = append(dirs, scanDir{scope: "project", dir: filepath.Join(cwd, ".zot", "extensions")})
211+ dirs = append(dirs, scanDir{scope: "project", dir: filepath.Join(cwd, ".zut", "extensions")})
212212 }
213- if h := ZotHome(); h != "" {
213+ if h := ZutHome(); h != "" {
214214 dirs = append(dirs, scanDir{scope: "global", dir: filepath.Join(h, "extensions")})
215215 }
216216
@@ -357,17 +357,17 @@ func printExtDoctorRow(w io.Writer, row extDoctorStaticRow, diag extensions.Exte
357357 }
358358
359359 func extDoctorLogPath(name string) string {
360- if name == "" || ZotHome() == "" {
360+ if name == "" || ZutHome() == "" {
361361 return ""
362362 }
363- return filepath.Join(ZotHome(), "logs", "ext-"+name+".log")
363+ return filepath.Join(ZutHome(), "logs", "ext-"+name+".log")
364364 }
365365
366366 // extLogs locates the named extension's log file and either cats or
367367 // tails it (-f).
368368 func extLogs(args []string) error {
369369 if len(args) == 0 {
370- return fmt.Errorf("usage: zot ext logs <name> [-f]")
370+ return fmt.Errorf("usage: zut ext logs <name> [-f]")
371371 }
372372 name := args[0]
373373 follow := false
@@ -376,7 +376,7 @@ func extLogs(args []string) error {
376376 follow = true
377377 }
378378 }
379- logPath := filepath.Join(ZotHome(), "logs", "ext-"+name+".log")
379+ logPath := filepath.Join(ZutHome(), "logs", "ext-"+name+".log")
380380 if _, err := os.Stat(logPath); err != nil {
381381 return fmt.Errorf("no log for %q at %s", name, logPath)
382382 }
@@ -402,7 +402,7 @@ func extToggle(args []string, enabled bool) error {
402402 if !enabled {
403403 verb = "disable"
404404 }
405- return fmt.Errorf("usage: zot ext %s <name>", verb)
405+ return fmt.Errorf("usage: zut ext %s <name>", verb)
406406 }
407407 name := args[0]
408408 dir, err := findExtensionDir(name)
@@ -438,7 +438,7 @@ func extToggle(args []string, enabled bool) error {
438438 // prompt (skip with --yes).
439439 func extRemove(args []string) error {
440440 if len(args) == 0 {
441- return fmt.Errorf("usage: zot ext remove <name> [--yes]")
441+ return fmt.Errorf("usage: zut ext remove <name> [--yes]")
442442 }
443443 name := args[0]
444444 yes := false
@@ -468,7 +468,7 @@ func extRemove(args []string) error {
468468 }
469469
470470 // extInstall copies a local directory or shallow-clones a git URL
471-// into $ZOT_HOME/extensions/. It stages the install and validates the
471+// into $ZUT_HOME/extensions/. It stages the install and validates the
472472 // manifest plus any extension-local executable before reporting success.
473473 // Builds are never inferred or run implicitly; --build=go is an explicit
474474 // opt-in for local Go extensions.
@@ -482,7 +482,7 @@ func extInstallContext(ctx context.Context, args []string) error {
482482 return err
483483 }
484484 src := opts.source
485- dest := filepath.Join(ZotHome(), "extensions")
485+ dest := filepath.Join(ZutHome(), "extensions")
486486 if err := os.MkdirAll(dest, 0o755); err != nil {
487487 return err
488488 }
@@ -517,7 +517,7 @@ func extInstallContext(ctx context.Context, args []string) error {
517517 return fmt.Errorf("destination %s already exists; remove it first", out)
518518 }
519519
520- stageRoot, err := os.MkdirTemp(dest, ".zot-extension-install-")
520+ stageRoot, err := os.MkdirTemp(dest, ".zut-extension-install-")
521521 if err != nil {
522522 return fmt.Errorf("create install staging directory: %w", err)
523523 }
@@ -548,7 +548,7 @@ type extInstallOptions struct {
548548
549549 func parseExtInstallArgs(args []string) (extInstallOptions, error) {
550550 if len(args) == 0 {
551- return extInstallOptions{}, errors.New("usage: zot ext install [--build=go] <path|git-url>")
551+ return extInstallOptions{}, errors.New("usage: zut ext install [--build=go] <path|git-url>")
552552 }
553553
554554 var opts extInstallOptions
@@ -581,13 +581,13 @@ func parseExtInstallArgs(args []string) (extInstallOptions, error) {
581581 case strings.HasPrefix(arg, "-"):
582582 return extInstallOptions{}, fmt.Errorf("unknown ext install option %q", arg)
583583 case opts.source != "":
584- return extInstallOptions{}, errors.New("usage: zot ext install [--build=go] <path|git-url>")
584+ return extInstallOptions{}, errors.New("usage: zut ext install [--build=go] <path|git-url>")
585585 default:
586586 opts.source = arg
587587 }
588588 }
589589 if opts.source == "" {
590- return extInstallOptions{}, errors.New("usage: zot ext install [--build=go] <path|git-url>")
590+ return extInstallOptions{}, errors.New("usage: zut ext install [--build=go] <path|git-url>")
591591 }
592592 if opts.builder != "" && opts.builder != "go" {
593593 return extInstallOptions{}, fmt.Errorf("unsupported extension builder %q (currently supported: go)", opts.builder)
@@ -639,7 +639,7 @@ func extensionRelativeExecPath(dir, execName string) (string, error) {
639639 }
640640
641641 func installGitExtension(ctx context.Context, src, dest string) error {
642- stageRoot, err := os.MkdirTemp(dest, ".zot-extension-clone-")
642+ stageRoot, err := os.MkdirTemp(dest, ".zut-extension-clone-")
643643 if err != nil {
644644 return fmt.Errorf("create install staging directory: %w", err)
645645 }
@@ -733,14 +733,14 @@ func validateExtensionExecutable(dir string, manifest extensions.Manifest) error
733733 path, local := extensions.ResolveExecPath(dir, manifest.Exec)
734734 if !local {
735735 // Bare commands such as "go", "node", and "npx" are resolved
736- // from PATH when zot starts. They may be intentionally installed
736+ // from PATH when zut starts. They may be intentionally installed
737737 // before the runtime is present on the current machine.
738738 return nil
739739 }
740740 info, err := os.Stat(path)
741741 if err != nil {
742742 if os.IsNotExist(err) {
743- return fmt.Errorf("extension executable %q is missing; provide it before installing. Local Go extensions can opt in with `zot ext install --build=go <path>`", manifest.Exec)
743+ return fmt.Errorf("extension executable %q is missing; provide it before installing. Local Go extensions can opt in with `zut ext install --build=go <path>`", manifest.Exec)
744744 }
745745 return fmt.Errorf("stat extension executable %q: %w", manifest.Exec, err)
746746 }
@@ -755,11 +755,11 @@ func validateExtensionExecutable(dir string, manifest extensions.Manifest) error
755755
756756 func extensionDirs() map[string]string {
757757 out := map[string]string{}
758- if h := ZotHome(); h != "" {
758+ if h := ZutHome(); h != "" {
759759 out["global"] = filepath.Join(h, "extensions")
760760 }
761761 if cwd, err := os.Getwd(); err == nil {
762- out["project"] = filepath.Join(cwd, ".zot", "extensions")
762+ out["project"] = filepath.Join(cwd, ".zut", "extensions")
763763 }
764764 return out
765765 }
@@ -782,7 +782,7 @@ func dashIfEmpty(s string) string {
782782 }
783783
784784 // copyDir does a recursive copy of src to dst preserving file mode
785-// bits. Used by `zot ext install <local-path>`.
785+// bits. Used by `zut ext install <local-path>`.
786786 //
787787 // Entries matched by the source's root .gitignore are skipped, and
788788 // .git itself is always skipped. This keeps non-portable, regeneratable
M packages/agent/extcmd_test.go +21 -21
@@ -9,15 +9,15 @@ import (
99 "strings"
1010 "testing"
1111
12- "github.com/patriceckhart/zot/packages/agent/extensions"
12+ "github.com/bnema/zut/packages/agent/extensions"
1313 )
1414
15-// TestExtInstallDotSource verifies that `zot ext install .` resolves the
15+// TestExtInstallDotSource verifies that `zut ext install .` resolves the
1616 // source directory correctly instead of collapsing it to the extensions/
1717 // parent directory.
1818 func TestExtInstallDotSource(t *testing.T) {
1919 home := t.TempDir()
20- t.Setenv("ZOT_HOME", home)
20+ t.Setenv("ZUT_HOME", home)
2121
2222 // Pre-create extensions/ to mimic a normal first run.
2323 if err := os.MkdirAll(filepath.Join(home, "extensions"), 0o755); err != nil {
@@ -56,7 +56,7 @@ func TestExtInstallDotSource(t *testing.T) {
5656 // successfully when its manifest uses a PATH-resolved executable.
5757 func TestExtInstallNamedDir(t *testing.T) {
5858 home := t.TempDir()
59- t.Setenv("ZOT_HOME", home)
59+ t.Setenv("ZUT_HOME", home)
6060
6161 src := filepath.Join(t.TempDir(), "myext")
6262 if err := os.MkdirAll(src, 0o755); err != nil {
@@ -76,7 +76,7 @@ func TestExtInstallNamedDir(t *testing.T) {
7676
7777 func TestExtInstallThemeOnly(t *testing.T) {
7878 home := t.TempDir()
79- t.Setenv("ZOT_HOME", home)
79+ t.Setenv("ZUT_HOME", home)
8080
8181 src := filepath.Join(t.TempDir(), "theme-source")
8282 if err := os.MkdirAll(src, 0o755); err != nil {
@@ -102,7 +102,7 @@ func TestExtInstallThemeOnly(t *testing.T) {
102102
103103 func TestExtInstallRejectsMissingExecutable(t *testing.T) {
104104 home := t.TempDir()
105- t.Setenv("ZOT_HOME", home)
105+ t.Setenv("ZUT_HOME", home)
106106
107107 src := filepath.Join(t.TempDir(), "tasked-phases")
108108 if err := os.MkdirAll(src, 0o755); err != nil {
@@ -127,7 +127,7 @@ func TestExtInstallBuildGo(t *testing.T) {
127127 }
128128
129129 home := t.TempDir()
130- t.Setenv("ZOT_HOME", home)
130+ t.Setenv("ZUT_HOME", home)
131131 src := filepath.Join(t.TempDir(), "go-extension")
132132 if err := os.MkdirAll(src, 0o755); err != nil {
133133 t.Fatal(err)
@@ -165,7 +165,7 @@ func TestExtInstallBuildFailureLeavesNoDestination(t *testing.T) {
165165 }
166166
167167 home := t.TempDir()
168- t.Setenv("ZOT_HOME", home)
168+ t.Setenv("ZUT_HOME", home)
169169 src := filepath.Join(t.TempDir(), "broken-go")
170170 if err := os.MkdirAll(src, 0o755); err != nil {
171171 t.Fatal(err)
@@ -192,7 +192,7 @@ func TestExtInstallBuildFailureLeavesNoDestination(t *testing.T) {
192192 t.Fatal(err)
193193 }
194194 for _, entry := range entries {
195- if strings.HasPrefix(entry.Name(), ".zot-extension-install-") {
195+ if strings.HasPrefix(entry.Name(), ".zut-extension-install-") {
196196 t.Fatalf("failed build left staging directory %s", entry.Name())
197197 }
198198 }
@@ -240,7 +240,7 @@ func TestParseExtInstallArgs(t *testing.T) {
240240
241241 func TestExtInstallRejectsBuildForThemeOnly(t *testing.T) {
242242 home := t.TempDir()
243- t.Setenv("ZOT_HOME", home)
243+ t.Setenv("ZUT_HOME", home)
244244 src := filepath.Join(t.TempDir(), "theme-source")
245245 if err := os.MkdirAll(src, 0o755); err != nil {
246246 t.Fatal(err)
@@ -263,7 +263,7 @@ func TestExtInstallRejectsBuildForThemeOnly(t *testing.T) {
263263
264264 func TestExtInstallRejectsWhitespaceManifestName(t *testing.T) {
265265 home := t.TempDir()
266- t.Setenv("ZOT_HOME", home)
266+ t.Setenv("ZUT_HOME", home)
267267 src := filepath.Join(t.TempDir(), "whitespace-name")
268268 if err := os.MkdirAll(src, 0o755); err != nil {
269269 t.Fatal(err)
@@ -280,7 +280,7 @@ func TestExtInstallRejectsWhitespaceManifestName(t *testing.T) {
280280
281281 func TestExtInstallRejectsDotManifestName(t *testing.T) {
282282 home := t.TempDir()
283- t.Setenv("ZOT_HOME", home)
283+ t.Setenv("ZUT_HOME", home)
284284 src := filepath.Join(t.TempDir(), "dot-name")
285285 if err := os.MkdirAll(src, 0o755); err != nil {
286286 t.Fatal(err)
@@ -297,7 +297,7 @@ func TestExtInstallRejectsDotManifestName(t *testing.T) {
297297
298298 func TestExtInstallRejectsBuildForPATHExecutable(t *testing.T) {
299299 home := t.TempDir()
300- t.Setenv("ZOT_HOME", home)
300+ t.Setenv("ZUT_HOME", home)
301301 src := filepath.Join(t.TempDir(), "path-extension")
302302 if err := os.MkdirAll(src, 0o755); err != nil {
303303 t.Fatal(err)
@@ -321,7 +321,7 @@ func TestExtInstallRejectsNonExecutable(t *testing.T) {
321321 }
322322
323323 home := t.TempDir()
324- t.Setenv("ZOT_HOME", home)
324+ t.Setenv("ZUT_HOME", home)
325325 src := filepath.Join(t.TempDir(), "non-executable")
326326 if err := os.MkdirAll(src, 0o755); err != nil {
327327 t.Fatal(err)
@@ -347,7 +347,7 @@ func TestExtInstallRejectsNonExecutable(t *testing.T) {
347347 // the manifest name controls the installed directory.
348348 func TestExtInstallCopiesIgnoredExecutableAndUsesManifestName(t *testing.T) {
349349 home := t.TempDir()
350- t.Setenv("ZOT_HOME", home)
350+ t.Setenv("ZUT_HOME", home)
351351
352352 src := filepath.Join(t.TempDir(), "source-dir")
353353 if err := os.MkdirAll(src, 0o755); err != nil {
@@ -434,7 +434,7 @@ func TestGitignoreNegation(t *testing.T) {
434434
435435 func TestExtDoctorStaticScanAndRender(t *testing.T) {
436436 home := t.TempDir()
437- t.Setenv("ZOT_HOME", home)
437+ t.Setenv("ZUT_HOME", home)
438438 cwd := t.TempDir()
439439 oldCWD, err := os.Getwd()
440440 if err != nil {
@@ -455,11 +455,11 @@ func TestExtDoctorStaticScanAndRender(t *testing.T) {
455455 }
456456 }
457457
458- mustWrite(filepath.Join(cwd, ".zot", "extensions", "disabled", "extension.json"), `{"name":"disabled","exec":"./run.sh","enabled":false}`)
459- mustWrite(filepath.Join(cwd, ".zot", "extensions", "theme", "extension.json"), `{"name":"theme"}`)
460- mustWrite(filepath.Join(cwd, ".zot", "extensions", "theme", "theme.json"), `{"name":"Theme"}`)
461- mustWrite(filepath.Join(cwd, ".zot", "extensions", "bad", "extension.json"), `{bad json`)
462- mustWrite(filepath.Join(cwd, ".zot", "extensions", "dup", "extension.json"), `{"name":"dup","exec":"./project.sh"}`)
458+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "disabled", "extension.json"), `{"name":"disabled","exec":"./run.sh","enabled":false}`)
459+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "theme", "extension.json"), `{"name":"theme"}`)
460+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "theme", "theme.json"), `{"name":"Theme"}`)
461+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "bad", "extension.json"), `{bad json`)
462+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "dup", "extension.json"), `{"name":"dup","exec":"./project.sh"}`)
463463 mustWrite(filepath.Join(home, "extensions", "dup", "extension.json"), `{"name":"dup","exec":"./global.sh"}`)
464464
465465 rows := scanExtDoctorStatic()
M packages/agent/extensions/events.go +3 -3
@@ -8,7 +8,7 @@ import (
88 "time"
99 "unicode/utf8"
1010
11- "github.com/patriceckhart/zot/packages/agent/extproto"
11+ "github.com/bnema/zut/packages/agent/extproto"
1212 )
1313
1414 // EmitEvent fires a one-way lifecycle event to every extension that
@@ -304,7 +304,7 @@ func (m *Manager) askIntercept(ctx context.Context, ext *Extension, payload extp
304304 ext.mu.Lock()
305305 delete(ext.pendingIntercept, id)
306306 ext.mu.Unlock()
307- fmt.Fprintf(ext.logFile, "[zot] intercept write failed: %v\n", err)
307+ fmt.Fprintf(ext.logFile, "[zut] intercept write failed: %v\n", err)
308308 return InterceptResult{}
309309 }
310310
@@ -321,7 +321,7 @@ func (m *Manager) askIntercept(ctx context.Context, ext *Extension, payload extp
321321 ext.mu.Lock()
322322 delete(ext.pendingIntercept, id)
323323 ext.mu.Unlock()
324- fmt.Fprintf(ext.logFile, "[zot] intercept %s timed out; allowing\n", payload.Event)
324+ fmt.Fprintf(ext.logFile, "[zut] intercept %s timed out; allowing\n", payload.Event)
325325 return InterceptResult{}
326326 case <-ctx.Done():
327327 ext.mu.Lock()
M packages/agent/extensions/intercept_test.go +3 -3
@@ -39,7 +39,7 @@ func TestInterceptAllThreeEvents(t *testing.T) {
3939 extDir := t.TempDir()
4040 script := `#!/bin/bash
4141 emit() { printf '%s\n' "$1"; }
42-emit '{"type":"hello","name":"itest","version":"0.1.0","capabilities":["events"]}'
42+emit '{"type":"hello","protocol_version":2,"name":"itest","version":"0.1.0","capabilities":["events"]}'
4343 emit '{"type":"subscribe","events":[],"intercept":["tool_call","turn_start","assistant_message"]}'
4444 emit '{"type":"ready"}'
4545 while IFS= read -r line; do
@@ -83,7 +83,7 @@ done
8383 t.Fatal(err)
8484 }
8585
86- // ZotHome is unused here; we load the extension explicitly.
86+ // ZutHome is unused here; we load the extension explicitly.
8787 m := New(t.TempDir(), "", "0.0.0-test", "anthropic", "claude-test", nil)
8888 t.Cleanup(func() { m.Stop(2 * time.Second) })
8989
@@ -159,7 +159,7 @@ func TestReloadRespawnsExtensions(t *testing.T) {
159159 extDir := t.TempDir()
160160 script := `#!/bin/bash
161161 emit() { printf '%s\n' "$1"; }
162-emit '{"type":"hello","name":"rtest","version":"0.1.0","capabilities":["commands"]}'
162+emit '{"type":"hello","protocol_version":2,"name":"rtest","version":"0.1.0","capabilities":["commands"]}'
163163 emit '{"type":"register_command","name":"rtest","description":"test"}'
164164 emit '{"type":"ready"}'
165165 while IFS= read -r line; do
M packages/agent/extensions/manager.go +40 -37
@@ -1,11 +1,11 @@
1-// Package extensions implements the host side of zot's subprocess
1+// Package extensions implements the host side of zut's subprocess
22 // extension protocol. The Manager discovers extensions in well-known
33 // directories, spawns each one, completes the hello handshake, and
44 // routes slash commands to the right extension.
55 //
6-// Each extension is its own process, communicating with zot over its
6+// Each extension is its own process, communicating with zut over its
77 // stdin/stdout in newline-delimited JSON. Stderr is redirected to a
8-// per-extension log file under $ZOT_HOME/logs/. Crashing one
8+// per-extension log file under $ZUT_HOME/logs/. Crashing one
99 // extension does not affect the others or the host.
1010 //
1111 // See docs/extensions.md for the user-facing reference and
@@ -28,13 +28,13 @@ import (
2828 "syscall"
2929 "time"
3030
31- "github.com/patriceckhart/zot/packages/agent/extproto"
32- "github.com/patriceckhart/zot/packages/agent/skills"
33- "github.com/patriceckhart/zot/packages/tui"
31+ "github.com/bnema/zut/packages/agent/extproto"
32+ "github.com/bnema/zut/packages/agent/skills"
33+ "github.com/bnema/zut/packages/tui"
3434 )
3535
3636 // Manifest is the extension.json file shipped alongside an
37-// extension's executable. It tells zot how to launch the extension
37+// extension's executable. It tells zut how to launch the extension
3838 // and provides display metadata.
3939 type Manifest struct {
4040 Name string `json:"name"`
@@ -49,7 +49,7 @@ type Manifest struct {
4949
5050 // IsEnabled returns the manifest's effective enabled state. Default
5151 // is true so adding a new extension folder Just Works without an
52-// extra zot ext enable command.
52+// extra zut ext enable command.
5353 func (m Manifest) IsEnabled() bool {
5454 return m.Enabled == nil || *m.Enabled
5555 }
@@ -96,7 +96,7 @@ func loadManifestSkills(extDir string, mf Manifest) ([]*skills.Skill, []error) {
9696 return loaded, append(errs, loadErrs...)
9797 }
9898
99-// Extension is a running extension subprocess and the metadata zot
99+// Extension is a running extension subprocess and the metadata zut
100100 // tracks about it.
101101 type Extension struct {
102102 Manifest Manifest
@@ -211,11 +211,11 @@ type commandRegistration struct {
211211 name string
212212 }
213213
214-// Manager owns every extension subprocess for the lifetime of zot.
214+// Manager owns every extension subprocess for the lifetime of zut.
215215 type Manager struct {
216- zotHome string
216+ zutHome string
217217 cwd string
218- zotVersion string
218+ zutVersion string
219219 provider string
220220 model string
221221 hooks HostHooks
@@ -254,11 +254,11 @@ type sessionEventState struct {
254254
255255 // New constructs an empty Manager. Call Discover to populate it from
256256 // the on-disk extension directories.
257-func New(zotHome, cwd, zotVersion, provider, model string, hooks HostHooks) *Manager {
257+func New(zutHome, cwd, zutVersion, provider, model string, hooks HostHooks) *Manager {
258258 return &Manager{
259- zotHome: zotHome,
259+ zutHome: zutHome,
260260 cwd: cwd,
261- zotVersion: zotVersion,
261+ zutVersion: zutVersion,
262262 provider: provider,
263263 model: model,
264264 hooks: hooks,
@@ -324,10 +324,10 @@ func (m *Manager) Discover(ctx context.Context) []error {
324324 func (m *Manager) searchDirs() []string {
325325 var dirs []string
326326 if m.cwd != "" {
327- dirs = append(dirs, filepath.Join(m.cwd, ".zot", "extensions"))
327+ dirs = append(dirs, filepath.Join(m.cwd, ".zut", "extensions"))
328328 }
329- if m.zotHome != "" {
330- dirs = append(dirs, filepath.Join(m.zotHome, "extensions"))
329+ if m.zutHome != "" {
330+ dirs = append(dirs, filepath.Join(m.zutHome, "extensions"))
331331 }
332332 return dirs
333333 }
@@ -352,7 +352,7 @@ func (m *Manager) loadOne(ctx context.Context, dir string) error {
352352 return errors.New("manifest: exec is required")
353353 }
354354 if !mf.IsEnabled() {
355- // Quietly skip disabled extensions; zot ext list will show them.
355+ // Quietly skip disabled extensions; zut ext list will show them.
356356 return nil
357357 }
358358
@@ -400,8 +400,8 @@ func (m *Manager) loadOne(ctx context.Context, dir string) error {
400400 }
401401
402402 // LoadExplicit loads each path as an ad-hoc extension. Used for
403-// `zot --ext <path>` so extension authors can iterate on a working
404-// copy without having to `zot ext install` after every change.
403+// `zut --ext <path>` so extension authors can iterate on a working
404+// copy without having to `zut ext install` after every change.
405405 //
406406 // Loaded BEFORE Discover so explicit paths win on name conflicts
407407 // against installed extensions. Spawns happen in parallel like the
@@ -540,7 +540,7 @@ func (m *Manager) Reload(ctx context.Context, grace time.Duration) ReloadStats {
540540 stats.Loaded = len(m.ext)
541541 m.mu.RUnlock()
542542
543- // Wait for ready frames. Use the same 3s grace zot uses at
543+ // Wait for ready frames. Use the same 3s grace zut uses at
544544 // startup so the reload feels no slower than a cold boot.
545545 readyDeadline := time.Now().Add(grace)
546546 if time.Until(readyDeadline) < 3*time.Second {
@@ -570,7 +570,7 @@ func (m *Manager) Reload(ctx context.Context, grace time.Duration) ReloadStats {
570570 //
571571 // Waits run in parallel: total time is max(per-extension wait), not
572572 // sum. Without this, a single slow extension (e.g. `npx tsx` cold)
573-// would gate every other extension's wait too and zot startup would
573+// would gate every other extension's wait too and zut startup would
574574 // scale linearly with the number of slow runtimes installed.
575575 //
576576 // Call after Discover and before relying on tool registrations.
@@ -595,7 +595,7 @@ func (m *Manager) WaitForReady(grace time.Duration) {
595595 ext.readyTimedOut = true
596596 ext.diagnostics = append(ext.diagnostics, "timed out waiting for ready frame")
597597 ext.mu.Unlock()
598- fmt.Fprintf(ext.logFile, "[zot] timed out waiting for ready frame; proceeding\n")
598+ fmt.Fprintf(ext.logFile, "[zut] timed out waiting for ready frame; proceeding\n")
599599 ext.readyOnce.Do(func() { close(ext.readyCh) })
600600 }
601601 }(ext)
@@ -628,7 +628,7 @@ func ResolveExecPath(extDir, execName string) (string, bool) {
628628 // runs the synchronous portion of the hello handshake. Asynchronous
629629 // frames are processed in a goroutine started here.
630630 func (m *Manager) spawn(ctx context.Context, ext *Extension) error {
631- logsDir := filepath.Join(m.zotHome, "logs")
631+ logsDir := filepath.Join(m.zutHome, "logs")
632632 _ = os.MkdirAll(logsDir, 0o755)
633633 logPath := filepath.Join(logsDir, "ext-"+ext.Manifest.Name+".log")
634634 logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
@@ -637,7 +637,7 @@ func (m *Manager) spawn(ctx context.Context, ext *Extension) error {
637637 }
638638 ext.LogPath = logPath
639639 ext.logFile = logFile
640- fmt.Fprintf(logFile, "\n[zot] starting %s/%s at %s\n", ext.Manifest.Name, ext.Manifest.Version, time.Now().Format(time.RFC3339))
640+ fmt.Fprintf(logFile, "\n[zut] starting %s/%s at %s\n", ext.Manifest.Name, ext.Manifest.Version, time.Now().Format(time.RFC3339))
641641
642642 // Bare names (node, npx, python3, tsx, etc.) are looked up via PATH;
643643 // absolute and extension-relative paths are resolved by the helper.
@@ -707,6 +707,9 @@ func (m *Manager) spawn(ctx context.Context, ext *Extension) error {
707707 if hello.Type != "hello" || hello.Name == "" {
708708 return fmt.Errorf("first frame must be hello (got %q; stderr log: %s)", hello.Type, logPath)
709709 }
710+ if hello.ProtocolVersion != extproto.ProtocolVersion {
711+ return fmt.Errorf("unsupported extension protocol version %d (want %d; stderr log: %s)", hello.ProtocolVersion, extproto.ProtocolVersion, logPath)
712+ }
710713 // Trust the manifest's name; ignore mismatch from the hello.
711714 ext.helloAck = true
712715
@@ -719,7 +722,7 @@ func (m *Manager) spawn(ctx context.Context, ext *Extension) error {
719722 ack, _ := extproto.Encode(extproto.HelloAckFromHost{
720723 Type: "hello_ack",
721724 ProtocolVersion: extproto.ProtocolVersion,
722- ZotVersion: m.zotVersion,
725+ ZutVersion: m.zutVersion,
723726 Provider: m.provider,
724727 Model: m.model,
725728 CWD: m.cwd,
@@ -800,7 +803,7 @@ func (m *Manager) assumeReadyAfterIdle(ext *Extension) {
800803 ext.autoReady = true
801804 ext.diagnostics = append(ext.diagnostics, "no ready frame; auto-ready after idle")
802805 ext.mu.Unlock()
803- fmt.Fprintf(ext.logFile, "[zot] no ready frame; auto-readying after idle (legacy SDK?)\n")
806+ fmt.Fprintf(ext.logFile, "[zut] no ready frame; auto-readying after idle (legacy SDK?)\n")
804807 close(ext.readyCh)
805808 })
806809 return
@@ -833,7 +836,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
833836 }
834837 ext.stopLifecycleWriter()
835838 ext.readyOnce.Do(func() { close(ext.readyCh) })
836- fmt.Fprintf(ext.logFile, "[zot] extension %s read loop exited at %s\n", ext.Manifest.Name, time.Now().Format(time.RFC3339))
839+ fmt.Fprintf(ext.logFile, "[zut] extension %s read loop exited at %s\n", ext.Manifest.Name, time.Now().Format(time.RFC3339))
837840 }()
838841
839842 for scanner.Scan() {
@@ -844,7 +847,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
844847 var frame extproto.Frame
845848 if err := json.Unmarshal(line, &frame); err != nil {
846849 ext.recordDiagnostic(fmt.Sprintf("malformed json frame: %v", err))
847- fmt.Fprintf(ext.logFile, "[zot] malformed json from extension: %v\n", err)
850+ fmt.Fprintf(ext.logFile, "[zut] malformed json from extension: %v\n", err)
848851 continue
849852 }
850853 switch frame.Type {
@@ -870,7 +873,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
870873 case "register_tool":
871874 var rt extproto.RegisterToolFromExt
872875 if err := json.Unmarshal(line, &rt); err != nil {
873- fmt.Fprintf(ext.logFile, "[zot] bad register_tool frame: %v\n", err)
876+ fmt.Fprintf(ext.logFile, "[zut] bad register_tool frame: %v\n", err)
874877 continue
875878 }
876879 // Validate the schema parses as JSON. If not, refuse to
@@ -879,7 +882,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
879882 var tmp any
880883 if err := json.Unmarshal(rt.Schema, &tmp); err != nil {
881884 ext.recordDiagnostic(fmt.Sprintf("tool %q schema is not valid json: %v", rt.Name, err))
882- fmt.Fprintf(ext.logFile, "[zot] tool %q: schema is not valid json (%v); skipped\n", rt.Name, err)
885+ fmt.Fprintf(ext.logFile, "[zut] tool %q: schema is not valid json (%v); skipped\n", rt.Name, err)
883886 continue
884887 }
885888 }
@@ -993,7 +996,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
993996 if strings.HasPrefix(text, "/") && m.hooks != nil {
994997 m.hooks.SubmitSlash(text)
995998 } else {
996- fmt.Fprintf(ext.logFile, "[zot] submit_slash refused (not a slash command): %q\n", s.Text)
999+ fmt.Fprintf(ext.logFile, "[zut] submit_slash refused (not a slash command): %q\n", s.Text)
9971000 }
9981001 }
9991002 case "command_response":
@@ -1031,7 +1034,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
10311034 // Caller of Stop is waiting on the process exit, not this frame.
10321035 default:
10331036 ext.recordDiagnostic(fmt.Sprintf("unknown frame type %q", frame.Type))
1034- fmt.Fprintf(ext.logFile, "[zot] unknown frame type %q\n", frame.Type)
1037+ fmt.Fprintf(ext.logFile, "[zut] unknown frame type %q\n", frame.Type)
10351038 }
10361039 }
10371040 }
@@ -1177,7 +1180,7 @@ type ExtensionDiagnostic struct {
11771180 }
11781181
11791182 // Diagnostics returns a snapshot of loaded extension state for commands such
1180-// as `zot ext doctor`. It is read-only and does not change manager behavior.
1183+// as `zut ext doctor`. It is read-only and does not change manager behavior.
11811184 func (m *Manager) Diagnostics() []ExtensionDiagnostic {
11821185 m.mu.RLock()
11831186 defer m.mu.RUnlock()
@@ -1253,7 +1256,7 @@ func (m *Manager) Commands() []CommandInfo {
12531256 }
12541257
12551258 // CommandInfo is one extension-registered slash command, surfaced to
1256-// the rest of zot for display purposes.
1259+// the rest of zut for display purposes.
12571260 type CommandInfo struct {
12581261 Extension string
12591262 Name string
@@ -1509,7 +1512,7 @@ func stopExtensions(exts []*Extension, gracePeriod time.Duration) {
15091512 }
15101513
15111514 // All returns every extension currently tracked, enabled or not.
1512-// Used by `zot ext list`.
1515+// Used by `zut ext list`.
15131516 func (m *Manager) All() []*Extension {
15141517 m.mu.RLock()
15151518 defer m.mu.RUnlock()
M packages/agent/extensions/manager_test.go +36 -7
@@ -6,7 +6,7 @@ import (
66 "io"
77 "os"
88
9- "github.com/patriceckhart/zot/packages/agent/extproto"
9+ "github.com/bnema/zut/packages/agent/extproto"
1010 "path/filepath"
1111 "runtime"
1212 "strings"
@@ -123,7 +123,7 @@ func writeMockExtension(t *testing.T, root string) {
123123 // stdin closes; tail's -F keeps the pipe alive long enough for
124124 // the manager to send command_invoked.
125125 script := `#!/bin/sh
126-printf '%s\n' '{"type":"hello","name":"mock","version":"0.0.1","capabilities":["commands"]}'
126+printf '%s\n' '{"type":"hello","protocol_version":2,"name":"mock","version":"0.0.1","capabilities":["commands"]}'
127127 printf '%s\n' '{"type":"register_command","name":"Ping","description":"ping/pong"}'
128128 while IFS= read -r line; do
129129 case "$line" in
@@ -260,6 +260,35 @@ func TestSpawnCleansUpProcessAfterInvalidHello(t *testing.T) {
260260 }
261261 }
262262
263+func TestSpawnRejectsUnsupportedExtensionProtocolVersion(t *testing.T) {
264+ if runtime.GOOS == "windows" {
265+ t.Skip("mock extension uses /bin/sh; skip on windows")
266+ }
267+
268+ tmp := t.TempDir()
269+ extDir := filepath.Join(tmp, "broken")
270+ if err := os.MkdirAll(extDir, 0o755); err != nil {
271+ t.Fatal(err)
272+ }
273+ script := `#!/bin/sh
274+printf '%s\n' '{"type":"hello","protocol_version":1,"name":"broken"}'
275+while IFS= read -r line; do :; done
276+`
277+ if err := os.WriteFile(filepath.Join(extDir, "run.sh"), []byte(script), 0o755); err != nil {
278+ t.Fatal(err)
279+ }
280+
281+ ext := &Extension{Manifest: Manifest{Name: "broken", Exec: "./run.sh"}, Dir: extDir}
282+ mgr := New(tmp, "", "0.0.0-test", "", "", nil)
283+ err := mgr.spawn(context.Background(), ext)
284+ if err == nil || !strings.Contains(err.Error(), "unsupported extension protocol version 1") {
285+ t.Fatalf("spawn error = %v", err)
286+ }
287+ if ext.cmd == nil || ext.cmd.ProcessState == nil {
288+ t.Fatal("failed protocol handshake process was not reaped")
289+ }
290+}
291+
263292 func TestDiscoverLoadsThemeOnlyExtension(t *testing.T) {
264293 tmp := t.TempDir()
265294 extDir := filepath.Join(tmp, "extensions", "theme-only")
@@ -423,7 +452,7 @@ func TestDiagnosticsReportMalformedFramesAndConflicts(t *testing.T) {
423452 }
424453
425454 writeDiagExtension("a-first", `#!/bin/sh
426-printf '%s\n' '{"type":"hello","name":"a-first","version":"0.1","capabilities":["commands","tools"]}'
455+printf '%s\n' '{"type":"hello","protocol_version":2,"name":"a-first","version":"0.1","capabilities":["commands","tools"]}'
427456 printf '%s\n' '{"type":"register_command","name":"CaseTest","description":"first"}'
428457 printf '%s\n' '{"type":"register_tool","name":"shared","description":"first","schema":{"type":"object"}}'
429458 printf '%s\n' '{"type":"register_tool","name":"broken","description":"bad","schema":'
@@ -435,7 +464,7 @@ while IFS= read -r line; do
435464 done
436465 `)
437466 writeDiagExtension("b-second", `#!/bin/sh
438-printf '%s\n' '{"type":"hello","name":"b-second","version":"0.1","capabilities":["commands","tools"]}'
467+printf '%s\n' '{"type":"hello","protocol_version":2,"name":"b-second","version":"0.1","capabilities":["commands","tools"]}'
439468 printf '%s\n' '{"type":"register_command","name":"casetest","description":"second"}'
440469 printf '%s\n' '{"type":"register_tool","name":"shared","description":"second","schema":{"type":"object"}}'
441470 printf '%s\n' '{"type":"ready"}'
@@ -512,7 +541,7 @@ func TestSpontaneousSubmit(t *testing.T) {
512541 }
513542
514543 script := `#!/bin/sh
515-printf '%s\n' '{"type":"hello","name":"submit-mock","version":"0.1","capabilities":["submit"]}'
544+printf '%s\n' '{"type":"hello","protocol_version":2,"name":"submit-mock","version":"0.1","capabilities":["submit"]}'
516545 printf '%s\n' '{"type":"ready"}'
517546 printf '%s\n' '{"type":"submit","text":" explain this repository briefly "}'
518547 printf '%s\n' '{"type":"submit","text":" "}'
@@ -575,7 +604,7 @@ func TestSpontaneousAlert(t *testing.T) {
575604 }
576605
577606 script := `#!/bin/sh
578-printf '%s\n' '{"type":"hello","name":"alert-mock","version":"0.1","capabilities":["alerts"]}'
607+printf '%s\n' '{"type":"hello","protocol_version":2,"name":"alert-mock","version":"0.1","capabilities":["alerts"]}'
579608 printf '%s\n' '{"type":"ready"}'
580609 printf '%s\n' '{"type":"alert","kind":"bell","reason":"question_ready"}'
581610 while IFS= read -r line; do
@@ -643,7 +672,7 @@ func TestSpontaneousOpenPanel(t *testing.T) {
643672 // Extension emits hello + ready, then immediately fires a
644673 // spontaneous open_panel, then waits for shutdown.
645674 script := `#!/bin/sh
646-printf '%s\n' '{"type":"hello","name":"panel-mock","version":"0.1","capabilities":["panels"]}'
675+printf '%s\n' '{"type":"hello","protocol_version":2,"name":"panel-mock","version":"0.1","capabilities":["panels"]}'
647676 printf '%s\n' '{"type":"ready"}'
648677 printf '%s\n' '{"type":"open_panel","panel":{"id":"test-panel","title":"Hello Panel","lines":["line one","line two"],"footer":"esc close"}}'
649678 while IFS= read -r line; do
M packages/agent/extensions/proc_unix.go +3 -3
@@ -7,12 +7,12 @@ import (
77 "syscall"
88 )
99
10-// isolateExtensionProcess moves extension subprocesses out of zot's
10+// isolateExtensionProcess moves extension subprocesses out of zut's
1111 // foreground process group. Some terminals, including kitty, inspect
1212 // foreground-process-group members when deciding the cwd for "open new
1313 // tab/split here". Extensions run with cmd.Dir set to their own install
14-// directory, so leaving them in zot's process group can make the terminal
15-// believe the tab cwd is an extension directory even after zot reports the
14+// directory, so leaving them in zut's process group can make the terminal
15+// believe the tab cwd is an extension directory even after zut reports the
1616 // project cwd with OSC 7.
1717 func isolateExtensionProcess(cmd *exec.Cmd) {
1818 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
M packages/agent/extensions/tool.go +2 -2
@@ -6,8 +6,8 @@ import (
66 "fmt"
77 "time"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 // extensionTool wraps a single extension-registered tool as a
M packages/agent/extensions/tool_test.go +1 -1
@@ -23,7 +23,7 @@ func writeMockToolExtension(t *testing.T, root string) {
2323 // Shell-script extension: hello, register one tool, ready, then
2424 // loop on stdin echoing back tool_call args as tool_result text.
2525 script := `#!/bin/sh
26-printf '%s\n' '{"type":"hello","name":"tool-mock","version":"0.1","capabilities":["tools"]}'
26+printf '%s\n' '{"type":"hello","protocol_version":2,"name":"tool-mock","version":"0.1","capabilities":["tools"]}'
2727 printf '%s\n' '{"type":"register_tool","name":"echo","description":"echo back the args","schema":{"type":"object","properties":{"msg":{"type":"string"}}}}'
2828 printf '%s\n' '{"type":"ready"}'
2929 while IFS= read -r line; do
M packages/agent/extproto/extproto.go +12 -9
@@ -1,5 +1,5 @@
11 // Package extproto defines the JSON-over-stdin/stdout wire format
2-// spoken between zot and its extension subprocesses. Both the host
2+// spoken between zut and its extension subprocesses. Both the host
33 // (packages/agent/extensions) and the SDK (packages/agent/ext) marshal/
44 // unmarshal the same types, so changes here ripple through both.
55 //
@@ -7,8 +7,8 @@
77 // boundaries follow newline boundaries; no multi-line JSON.
88 //
99 // Direction conventions in this file:
10-// - Type names ending in "FromExt" are sent by the extension to zot.
11-// - Type names ending in "FromHost" are sent by zot to the extension.
10+// - Type names ending in "FromExt" are sent by the extension to zut.
11+// - Type names ending in "FromHost" are sent by zut to the extension.
1212 // - Names without a suffix are direction-neutral payloads or shared
1313 // value types.
1414 //
@@ -22,7 +22,9 @@ import (
2222 "strings"
2323 )
2424
25-const ProtocolVersion = 1
25+// ProtocolVersion is the extension protocol major. Version 2 introduces the
26+// zut_version wire key and requires both sides to reject mismatched majors.
27+const ProtocolVersion = 2
2628
2729 type Frame struct {
2830 Type string `json:"type"`
@@ -30,10 +32,11 @@ type Frame struct {
3032 }
3133
3234 type HelloFromExt struct {
33- Type string `json:"type"`
34- Name string `json:"name"`
35- Version string `json:"version"`
36- Capabilities []string `json:"capabilities,omitempty"`
35+ Type string `json:"type"`
36+ ProtocolVersion int `json:"protocol_version"`
37+ Name string `json:"name"`
38+ Version string `json:"version"`
39+ Capabilities []string `json:"capabilities,omitempty"`
3740 }
3841
3942 type RegisterCommandFromExt struct {
@@ -228,7 +231,7 @@ type ShutdownAckFromExt struct {
228231 type HelloAckFromHost struct {
229232 Type string `json:"type"`
230233 ProtocolVersion int `json:"protocol_version"`
231- ZotVersion string `json:"zot_version"`
234+ ZutVersion string `json:"zut_version"`
232235 Provider string `json:"provider"`
233236 Model string `json:"model"`
234237 CWD string `json:"cwd"`
M packages/agent/extupdate.go +8 -8
@@ -13,7 +13,7 @@ import (
1313 "time"
1414 )
1515
16-// updateAllExtensions iterates $ZOT_HOME/extensions/* and tries to
16+// updateAllExtensions iterates $ZUT_HOME/extensions/* and tries to
1717 // update each one in place via `git pull --ff-only`. Failures are
1818 // per-extension and never abort the loop; the caller can treat any
1919 // error here as advisory only.
@@ -38,14 +38,14 @@ import (
3838 // remote git URL would be a real footgun. Extension authors are
3939 // expected to commit a working binary (or instruct the user to
4040 // rebuild manually via /reload-ext + their own build).
41-func updateAllExtensions(zotHome string) {
42- dir := filepath.Join(zotHome, "extensions")
41+func updateAllExtensions(zutHome string) {
42+ dir := filepath.Join(zutHome, "extensions")
4343 entries, err := os.ReadDir(dir)
4444 if err != nil {
4545 if os.IsNotExist(err) {
4646 return // no extensions installed; nothing to do
4747 }
48- fmt.Fprintf(os.Stderr, "zot update: skipping extension update (read %s: %v)\n", dir, err)
48+ fmt.Fprintf(os.Stderr, "zut update: skipping extension update (read %s: %v)\n", dir, err)
4949 return
5050 }
5151
@@ -67,7 +67,7 @@ func updateAllExtensions(zotHome string) {
6767 }
6868
6969 fmt.Println()
70- fmt.Println("zot update: updating extensions...")
70+ fmt.Println("zut update: updating extensions...")
7171
7272 var updated, upToDate, skipped, failed int
7373 for _, name := range names {
@@ -85,7 +85,7 @@ func updateAllExtensions(zotHome string) {
8585 }
8686 }
8787
88- fmt.Printf("zot update: extensions: %d updated, %d up-to-date, %d skipped, %d failed\n",
88+ fmt.Printf("zut update: extensions: %d updated, %d up-to-date, %d skipped, %d failed\n",
8989 updated, upToDate, skipped, failed)
9090 }
9191
@@ -180,7 +180,7 @@ func updateOneExtension(extDir, name string) string {
180180 func gitStash(ctx context.Context, dir string) (stashRef string, stashed bool, err error) {
181181 // Tag the stash with a recognisable name so the user can find it
182182 // later if pop fails and they want to inspect it.
183- msg := fmt.Sprintf("zot-update-%d", time.Now().Unix())
183+ msg := fmt.Sprintf("zut-update-%d", time.Now().Unix())
184184 out, err := runGit(ctx, dir, "stash", "push", "--include-untracked", "-m", msg)
185185 if err != nil {
186186 return "", false, fmt.Errorf("%s", strings.TrimSpace(out))
@@ -196,7 +196,7 @@ func gitStash(ctx context.Context, dir string) (stashRef string, stashed bool, e
196196 if lerr == nil {
197197 for _, line := range strings.Split(listOut, "\n") {
198198 if strings.Contains(line, msg) {
199- // line looks like: "stash@{0}: On main: zot-update-1700000000"
199+ // line looks like: "stash@{0}: On main: zut-update-1700000000"
200200 if idx := strings.Index(line, ":"); idx > 0 {
201201 return strings.TrimSpace(line[:idx]), true, nil
202202 }
M packages/agent/extupdate_test.go +13 -13
@@ -66,8 +66,8 @@ func mustRun(t *testing.T, dir, name string, args ...string) {
6666 }
6767 }
6868
69-// makeFakeZotHome scaffolds $ZOT_HOME/extensions/ and returns the path.
70-func makeFakeZotHome(t *testing.T) string {
69+// makeFakeZutHome scaffolds $ZUT_HOME/extensions/ and returns the path.
70+func makeFakeZutHome(t *testing.T) string {
7171 t.Helper()
7272 home := t.TempDir()
7373 if err := os.MkdirAll(filepath.Join(home, "extensions"), 0o755); err != nil {
@@ -95,19 +95,19 @@ func extWithManifest(t *testing.T, root, name, content string) string {
9595 // ----- tests -----
9696
9797 func TestUpdateAllExtensions_NoExtensionsDirectory(t *testing.T) {
98- // Brand new $ZOT_HOME with no extensions/ directory at all.
98+ // Brand new $ZUT_HOME with no extensions/ directory at all.
9999 // Must not panic and must print nothing alarming.
100100 home := t.TempDir()
101101 updateAllExtensions(home) // no-op
102102 }
103103
104104 func TestUpdateAllExtensions_EmptyDirectory(t *testing.T) {
105- home := makeFakeZotHome(t)
105+ home := makeFakeZutHome(t)
106106 updateAllExtensions(home) // no-op, no panic
107107 }
108108
109109 func TestUpdateOneExtension_NoManifest(t *testing.T) {
110- home := makeFakeZotHome(t)
110+ home := makeFakeZutHome(t)
111111 dir := filepath.Join(home, "extensions", "noman")
112112 _ = os.MkdirAll(dir, 0o755)
113113 got := updateOneExtension(dir, "noman")
@@ -117,7 +117,7 @@ func TestUpdateOneExtension_NoManifest(t *testing.T) {
117117 }
118118
119119 func TestUpdateOneExtension_Disabled(t *testing.T) {
120- home := makeFakeZotHome(t)
120+ home := makeFakeZutHome(t)
121121 dir := extWithManifest(t, home, "off",
122122 `{"name":"off","version":"1.0.0","exec":"./x","enabled":false}`)
123123 got := updateOneExtension(dir, "off")
@@ -127,7 +127,7 @@ func TestUpdateOneExtension_Disabled(t *testing.T) {
127127 }
128128
129129 func TestUpdateOneExtension_NotAGitCheckout(t *testing.T) {
130- home := makeFakeZotHome(t)
130+ home := makeFakeZutHome(t)
131131 dir := extWithManifest(t, home, "plain", "")
132132 got := updateOneExtension(dir, "plain")
133133 if got != "skipped" {
@@ -138,7 +138,7 @@ func TestUpdateOneExtension_NotAGitCheckout(t *testing.T) {
138138 func TestUpdateOneExtension_UpToDate(t *testing.T) {
139139 gitAvailable(t)
140140 tmp := t.TempDir()
141- home := makeFakeZotHome(t)
141+ home := makeFakeZutHome(t)
142142
143143 // Set up a remote and clone it as the "installed" extension.
144144 remote := filepath.Join(tmp, "remote.git")
@@ -165,7 +165,7 @@ func TestUpdateOneExtension_UpToDate(t *testing.T) {
165165 func TestUpdateOneExtension_PullsNewCommit(t *testing.T) {
166166 gitAvailable(t)
167167 tmp := t.TempDir()
168- home := makeFakeZotHome(t)
168+ home := makeFakeZutHome(t)
169169
170170 remote := filepath.Join(tmp, "remote.git")
171171 initBareRepo(t, remote)
@@ -201,7 +201,7 @@ func TestUpdateOneExtension_PullsNewCommit(t *testing.T) {
201201 func TestUpdateOneExtension_StashesDirtyWorktree(t *testing.T) {
202202 gitAvailable(t)
203203 tmp := t.TempDir()
204- home := makeFakeZotHome(t)
204+ home := makeFakeZutHome(t)
205205
206206 remote := filepath.Join(tmp, "remote.git")
207207 initBareRepo(t, remote)
@@ -244,7 +244,7 @@ func TestUpdateOneExtension_StashesDirtyWorktree(t *testing.T) {
244244 func TestUpdateOneExtension_DivergedFails(t *testing.T) {
245245 gitAvailable(t)
246246 tmp := t.TempDir()
247- home := makeFakeZotHome(t)
247+ home := makeFakeZutHome(t)
248248
249249 remote := filepath.Join(tmp, "remote.git")
250250 initBareRepo(t, remote)
@@ -281,7 +281,7 @@ func TestUpdateOneExtension_DivergedFails(t *testing.T) {
281281 func TestUpdateOneExtension_BadRemoteFails(t *testing.T) {
282282 gitAvailable(t)
283283 tmp := t.TempDir()
284- home := makeFakeZotHome(t)
284+ home := makeFakeZutHome(t)
285285
286286 // Init a repo with an "origin" that points at a non-existent path.
287287 extDir := filepath.Join(home, "extensions", "bad")
@@ -312,7 +312,7 @@ func TestUpdateOneExtension_BadRemoteFails(t *testing.T) {
312312 func TestUpdateAllExtensions_MixedSet(t *testing.T) {
313313 gitAvailable(t)
314314 tmp := t.TempDir()
315- home := makeFakeZotHome(t)
315+ home := makeFakeZutHome(t)
316316
317317 // 1 plain non-git extension (skipped)
318318 extWithManifest(t, home, "plain", "")
M packages/agent/fast_mode_test.go +7 -7
@@ -6,7 +6,7 @@ import (
66 "strings"
77 "testing"
88
9- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/provider"
1010 )
1111
1212 func TestParseArgsFastModeChildFlag(t *testing.T) {
@@ -23,7 +23,7 @@ func TestParseArgsFastModeChildFlag(t *testing.T) {
2323 }
2424
2525 func TestResolveFastModeDefaultsOff(t *testing.T) {
26- t.Setenv("ZOT_HOME", t.TempDir())
26+ t.Setenv("ZUT_HOME", t.TempDir())
2727 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5"}); err != nil {
2828 t.Fatal(err)
2929 }
@@ -38,7 +38,7 @@ func TestResolveFastModeDefaultsOff(t *testing.T) {
3838 }
3939
4040 func TestResolveFastModeFromConfig(t *testing.T) {
41- t.Setenv("ZOT_HOME", t.TempDir())
41+ t.Setenv("ZUT_HOME", t.TempDir())
4242 enabled := true
4343 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", FastMode: &enabled}); err != nil {
4444 t.Fatal(err)
@@ -54,7 +54,7 @@ func TestResolveFastModeFromConfig(t *testing.T) {
5454 }
5555
5656 func TestResolveFastModeRemainsEnabledForUnsupportedProviderUntilRequest(t *testing.T) {
57- t.Setenv("ZOT_HOME", t.TempDir())
57+ t.Setenv("ZUT_HOME", t.TempDir())
5858 enabled := true
5959 if err := SaveConfig(Config{Provider: "anthropic", Model: "claude-sonnet-4-5", FastMode: &enabled}); err != nil {
6060 t.Fatal(err)
@@ -70,7 +70,7 @@ func TestResolveFastModeRemainsEnabledForUnsupportedProviderUntilRequest(t *test
7070 }
7171
7272 func TestResolveFastModeRequiresExplicitChildOverride(t *testing.T) {
73- t.Setenv("ZOT_HOME", t.TempDir())
73+ t.Setenv("ZUT_HOME", t.TempDir())
7474 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5"}); err != nil {
7575 t.Fatal(err)
7676 }
@@ -85,7 +85,7 @@ func TestResolveFastModeRequiresExplicitChildOverride(t *testing.T) {
8585 }
8686
8787 func TestResolveFastModeExplicitChildDisableOverridesConfig(t *testing.T) {
88- t.Setenv("ZOT_HOME", t.TempDir())
88+ t.Setenv("ZUT_HOME", t.TempDir())
8989 enabled := true
9090 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", FastMode: &enabled}); err != nil {
9191 t.Fatal(err)
@@ -105,7 +105,7 @@ func TestResolveFastModeExplicitChildDisableOverridesConfig(t *testing.T) {
105105 }
106106
107107 func TestConfigSettingsStorePersistsFastMode(t *testing.T) {
108- t.Setenv("ZOT_HOME", t.TempDir())
108+ t.Setenv("ZUT_HOME", t.TempDir())
109109 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
110110 t.Fatal(err)
111111 }
M packages/agent/instruction_context.go +2 -2
@@ -3,8 +3,8 @@ package agent
33 import (
44 "sort"
55
6- "github.com/patriceckhart/zot/packages/agent/extensions"
7- "github.com/patriceckhart/zot/packages/agent/skills"
6+ "github.com/bnema/zut/packages/agent/extensions"
7+ "github.com/bnema/zut/packages/agent/skills"
88 )
99
1010 // instructionContextPaths returns the loaded instruction paths in effective
M packages/agent/instruction_context_test.go +2 -2
@@ -4,8 +4,8 @@ import (
44 "reflect"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/agent/extensions"
8- "github.com/patriceckhart/zot/packages/agent/skills"
7+ "github.com/bnema/zut/packages/agent/extensions"
8+ "github.com/bnema/zut/packages/agent/skills"
99 )
1010
1111 func TestInstructionContextPathsPreserveLoadOrder(t *testing.T) {
M packages/agent/lsp/client.go +2 -2
@@ -164,7 +164,7 @@ func (c *Client) Initialize(ctx context.Context) error {
164164 },
165165 },
166166 "initializationOptions": c.options.InitializationOptions,
167- "clientInfo": map[string]any{"name": "zot", "version": "lsp"},
167+ "clientInfo": map[string]any{"name": "zut", "version": "lsp"},
168168 }
169169 result, err := c.Request(ctx, "initialize", params)
170170 if err != nil {
@@ -273,7 +273,7 @@ func (c *Client) DidOpen(path, languageID, text string) error {
273273 }
274274
275275 // DidChange sends a full-content change, which avoids version/range drift
276-// between zot and servers that use different incremental-sync policies.
276+// between zut and servers that use different incremental-sync policies.
277277 func (c *Client) DidChange(path, text string) error {
278278 uri, err := pathToURI(path)
279279 if err != nil {
M packages/agent/lsp/config.go +10 -10
@@ -64,14 +64,14 @@ func BuiltinServers() []ServerConfig {
6464
6565 // LoadConfig reads the global and project lsp.json files. Project files are
6666 // applied after the global file; within a project, lsp.json, .lsp.json, and
67-// .zot/lsp.json are applied in that order. Missing files are harmless.
67+// .zut/lsp.json are applied in that order. Missing files are harmless.
6868 func LoadConfig(cwd string) (Config, error) {
6969 cwd, err := absDir(cwd)
7070 if err != nil {
7171 return Config{}, err
7272 }
7373 var files []string
74- if home := zotHome(); home != "" {
74+ if home := zutHome(); home != "" {
7575 files = append(files, filepath.Join(home, "lsp.json"))
7676 }
7777 files = append(files,
@@ -79,7 +79,7 @@ func LoadConfig(cwd string) (Config, error) {
7979 filepath.Join(cwd, ".omp", "lsp.json"),
8080 filepath.Join(cwd, "lsp.json"),
8181 filepath.Join(cwd, ".lsp.json"),
82- filepath.Join(cwd, ".zot", "lsp.json"),
82+ filepath.Join(cwd, ".zut", "lsp.json"),
8383 )
8484
8585 objects := make(map[string]map[string]json.RawMessage)
@@ -341,12 +341,12 @@ func absDir(cwd string) (string, error) {
341341 return filepath.Clean(abs), nil
342342 }
343343
344-func zotHome() string {
345- if value := strings.TrimSpace(os.Getenv("ZOT_HOME")); value != "" {
344+func zutHome() string {
345+ if value := strings.TrimSpace(os.Getenv("ZUT_HOME")); value != "" {
346346 return value
347347 }
348348 if value := strings.TrimSpace(os.Getenv("XDG_STATE_HOME")); value != "" {
349- return filepath.Join(value, "zot")
349+ return filepath.Join(value, "zut")
350350 }
351351 switch runtime.GOOS {
352352 case "darwin":
@@ -354,17 +354,17 @@ func zotHome() string {
354354 if home == "" {
355355 return ""
356356 }
357- return filepath.Join(home, "Library", "Application Support", "zot")
357+ return filepath.Join(home, "Library", "Application Support", "zut")
358358 case "windows":
359359 if value := strings.TrimSpace(os.Getenv("LOCALAPPDATA")); value != "" {
360- return filepath.Join(value, "zot")
360+ return filepath.Join(value, "zut")
361361 }
362362 }
363363 home, _ := os.UserHomeDir()
364364 if home == "" {
365365 return ""
366366 }
367- return filepath.Join(home, ".local", "state", "zot")
367+ return filepath.Join(home, ".local", "state", "zut")
368368 }
369369
370370 func providerIDs(data []byte) map[string]struct{} {
@@ -480,7 +480,7 @@ func strconvQuote(s string) string {
480480 }
481481
482482 // flattenProviderObject adapts nested provider shapes to the flat
483-// ServerConfig fields used by zot. Unknown provider metadata remains
483+// ServerConfig fields used by zut. Unknown provider metadata remains
484484 // harmless JSON and is ignored by decodeServer.
485485 func flattenProviderObject(object map[string]json.RawMessage) {
486486 if _, exists := object["command"]; !exists {
M packages/agent/lsp/edits.go +1 -1
@@ -187,7 +187,7 @@ func prepareTextEdits(path string, edits []TextEdit) ([]byte, os.FileMode, error
187187 }
188188
189189 func writeTextEdits(path string, out []byte, mode os.FileMode) error {
190- tmp, err := os.CreateTemp(filepath.Dir(path), ".zot-lsp-edit-*")
190+ tmp, err := os.CreateTemp(filepath.Dir(path), ".zut-lsp-edit-*")
191191 if err != nil {
192192 return err
193193 }
M packages/agent/lsp/lsp_test.go +16 -16
@@ -16,7 +16,7 @@ import (
1616 )
1717
1818 func TestFakeLSPProcess(t *testing.T) {
19- if os.Getenv("ZOT_FAKE_LSP") != "1" {
19+ if os.Getenv("ZUT_FAKE_LSP") != "1" {
2020 return
2121 }
2222 reader := bufio.NewReader(os.Stdin)
@@ -56,7 +56,7 @@ func TestFakeLSPProcess(t *testing.T) {
5656 func TestClientFakeStdioRPC(t *testing.T) {
5757 root := t.TempDir()
5858 script := os.Args[0]
59- spec := ServerConfig{ID: "fake", Kind: "lsp", Command: script, Args: []string{"-test.run=TestFakeLSPProcess"}, Env: map[string]string{"ZOT_FAKE_LSP": "1"}}
59+ spec := ServerConfig{ID: "fake", Kind: "lsp", Command: script, Args: []string{"-test.run=TestFakeLSPProcess"}, Env: map[string]string{"ZUT_FAKE_LSP": "1"}}
6060 diagnostics := make(chan []Diagnostic, 1)
6161 client, err := NewClientWithContext(context.Background(), spec, root, ClientOptions{OnDiagnostics: func(value []Diagnostic) { diagnostics <- value }})
6262 if err != nil {
@@ -157,25 +157,25 @@ func TestFramingRoundTripWithAdditionalHeader(t *testing.T) {
157157 }
158158 }
159159
160-func TestZotHomeEnvironmentPrecedence(t *testing.T) {
161- configuredHome := filepath.Join(t.TempDir(), "zot")
160+func TestZutHomeEnvironmentPrecedence(t *testing.T) {
161+ configuredHome := filepath.Join(t.TempDir(), "zut")
162162 xdgHome := filepath.Join(t.TempDir(), "state")
163- t.Setenv("ZOT_HOME", configuredHome)
163+ t.Setenv("ZUT_HOME", configuredHome)
164164 t.Setenv("XDG_STATE_HOME", xdgHome)
165- if got := zotHome(); got != configuredHome {
166- t.Fatalf("ZOT_HOME path = %q, want %q", got, configuredHome)
165+ if got := zutHome(); got != configuredHome {
166+ t.Fatalf("ZUT_HOME path = %q, want %q", got, configuredHome)
167167 }
168- t.Setenv("ZOT_HOME", "")
169- if got := zotHome(); got != filepath.Join(xdgHome, "zot") {
170- t.Fatalf("XDG_STATE_HOME path = %q, want %q", got, filepath.Join(xdgHome, "zot"))
168+ t.Setenv("ZUT_HOME", "")
169+ if got := zutHome(); got != filepath.Join(xdgHome, "zut") {
170+ t.Fatalf("XDG_STATE_HOME path = %q, want %q", got, filepath.Join(xdgHome, "zut"))
171171 }
172172 }
173173
174174 func TestLoadConfigMergesGlobalProjectAndBuiltins(t *testing.T) {
175175 root := t.TempDir()
176- zotHome := t.TempDir()
177- t.Setenv("ZOT_HOME", zotHome)
178- if err := os.WriteFile(filepath.Join(zotHome, "lsp.json"), []byte(`{"servers":{"gopls":{"settings":{"global":true},"args":["--global"]}}}`), 0o644); err != nil {
176+ zutHome := t.TempDir()
177+ t.Setenv("ZUT_HOME", zutHome)
178+ if err := os.WriteFile(filepath.Join(zutHome, "lsp.json"), []byte(`{"servers":{"gopls":{"settings":{"global":true},"args":["--global"]}}}`), 0o644); err != nil {
179179 t.Fatal(err)
180180 }
181181 if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n"), 0o644); err != nil {
@@ -214,8 +214,8 @@ func TestLoadConfigMergesGlobalProjectAndBuiltins(t *testing.T) {
214214
215215 func TestLoadConfigAcceptsProviderArrays(t *testing.T) {
216216 root := t.TempDir()
217- zotHome := t.TempDir()
218- t.Setenv("ZOT_HOME", zotHome)
217+ zutHome := t.TempDir()
218+ t.Setenv("ZUT_HOME", zutHome)
219219 if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n"), 0o644); err != nil {
220220 t.Fatal(err)
221221 }
@@ -245,7 +245,7 @@ func TestLoadConfigAcceptsProviderArrays(t *testing.T) {
245245
246246 func TestLoadConfigAutoDetectFalseKeepsExplicitProviders(t *testing.T) {
247247 root := t.TempDir()
248- t.Setenv("ZOT_HOME", t.TempDir())
248+ t.Setenv("ZUT_HOME", t.TempDir())
249249 if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n"), 0o644); err != nil {
250250 t.Fatal(err)
251251 }
M packages/agent/lsp/protocol.go +1 -1
@@ -26,7 +26,7 @@ type TextEdit struct {
2626 NewText string `json:"newText"`
2727 }
2828
29-// WorkspaceEdit is the safe subset of a workspace edit understood by zot.
29+// WorkspaceEdit is the safe subset of a workspace edit understood by zut.
3030 // Resource operations in DocumentChanges are deliberately rejected.
3131 type WorkspaceEdit struct {
3232 Changes map[string][]TextEdit `json:"changes,omitempty"`
M packages/agent/modelsync.go +13 -13
@@ -8,17 +8,17 @@ import (
88 "strings"
99 "time"
1010
11- "github.com/patriceckhart/zot/packages/provider"
11+ "github.com/bnema/zut/packages/provider"
1212 )
1313
1414 // ModelCachePath returns the on-disk location of the merged model cache.
1515 func ModelCachePath() string {
16- return filepath.Join(ZotHome(), "models-cache.json")
16+ return filepath.Join(ZutHome(), "models-cache.json")
1717 }
1818
1919 // UserModelsPath returns the path to the user's models.json override.
2020 func UserModelsPath() string {
21- return filepath.Join(ZotHome(), "models.json")
21+ return filepath.Join(ZutHome(), "models.json")
2222 }
2323
2424 // LoadCachedModels loads the cache file and applies it to the provider
@@ -34,7 +34,7 @@ func LoadCachedModels() {
3434 }
3535 }
3636
37-// LoadUserModels reads $ZOT_HOME/models.json and merges any user-defined
37+// LoadUserModels reads $ZUT_HOME/models.json and merges any user-defined
3838 // models into the active catalog. User models take highest precedence.
3939 // Any validation issues (bad provider id, empty model id, malformed
4040 // JSON, negative widths) are surfaced as one warning per line on stderr;
@@ -42,14 +42,14 @@ func LoadCachedModels() {
4242 func LoadUserModels() {
4343 models, warnings := provider.LoadUserModelsWithWarnings(UserModelsPath())
4444 for _, w := range warnings {
45- fmt.Fprintln(os.Stderr, "zot:", w)
45+ fmt.Fprintln(os.Stderr, "zut:", w)
4646 }
4747 provider.SetUserModels(models)
4848 }
4949
5050 // isGatewayProvider returns true for providers whose OpenAI-compatible
51-// endpoint can accept routed model IDs that are not present in zot's local
52-// catalog. Vercel AI Gateway is intentionally not listed here: zot currently
51+// endpoint can accept routed model IDs that are not present in zut's local
52+// catalog. Vercel AI Gateway is intentionally not listed here: zut currently
5353 // talks to it through the Anthropic-compatible client, which still requires
5454 // catalog metadata for request shaping.
5555 func isGatewayProvider(prov string) bool {
@@ -81,20 +81,20 @@ func isGatewayRoutedModelID(model string) bool {
8181 // half-applied switch) -> reset model to the provider's default.
8282 //
8383 // Gateway providers are exempt from the cross-provider model check for routed
84-// model IDs because those IDs can be valid even when absent from zot's catalog.
84+// model IDs because those IDs can be valid even when absent from zut's catalog.
8585 //
8686 // Silent on success; one stderr line per repair. Errors loading or
8787 // saving the file are non-fatal — the caller continues with defaults.
8888 func ValidateAndRepairConfig() {
8989 cfg, err := LoadConfig()
9090 if err != nil {
91- fmt.Fprintf(os.Stderr, "zot: config.json: %v (using defaults)\n", err)
91+ fmt.Fprintf(os.Stderr, "zut: config.json: %v (using defaults)\n", err)
9292 return
9393 }
9494 changed := false
9595
9696 if cfg.Provider != "" && !isKnownProvider(cfg.Provider) {
97- fmt.Fprintf(os.Stderr, "zot: config.json: unknown provider %q reset to \"anthropic\"\n", cfg.Provider)
97+ fmt.Fprintf(os.Stderr, "zut: config.json: unknown provider %q reset to \"anthropic\"\n", cfg.Provider)
9898 cfg.Provider = "anthropic"
9999 cfg.Model = ""
100100 changed = true
@@ -110,7 +110,7 @@ func ValidateAndRepairConfig() {
110110 } else if m, err := provider.FindModel("", cfg.Model); err == nil {
111111 fix := defaultModelForProvider(cfg.Provider)
112112 fmt.Fprintf(os.Stderr,
113- "zot: config.json: model %q belongs to provider %q (config has provider=%q); switched model to %q\n",
113+ "zut: config.json: model %q belongs to provider %q (config has provider=%q); switched model to %q\n",
114114 cfg.Model, m.Provider, cfg.Provider, fix)
115115 cfg.Model = fix
116116 changed = true
@@ -118,7 +118,7 @@ func ValidateAndRepairConfig() {
118118 // Model id not in any catalog. Reset to provider's default.
119119 fix := defaultModelForProvider(cfg.Provider)
120120 fmt.Fprintf(os.Stderr,
121- "zot: config.json: model %q not found in the active catalog; switched to %q\n",
121+ "zut: config.json: model %q not found in the active catalog; switched to %q\n",
122122 cfg.Model, fix)
123123 cfg.Model = fix
124124 changed = true
@@ -128,7 +128,7 @@ func ValidateAndRepairConfig() {
128128
129129 if changed {
130130 if err := SaveConfig(cfg); err != nil {
131- fmt.Fprintf(os.Stderr, "zot: config.json: failed to persist repair: %v\n", err)
131+ fmt.Fprintf(os.Stderr, "zut: config.json: failed to persist repair: %v\n", err)
132132 }
133133 }
134134 }
M packages/agent/modelsync_test.go +9 -9
@@ -10,7 +10,7 @@ import (
1010 "path/filepath"
1111 "testing"
1212
13- "github.com/patriceckhart/zot/packages/provider"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 func TestRefreshLlamaCPPModelsAddsOnlyLoadedModels(t *testing.T) {
@@ -23,7 +23,7 @@ func TestRefreshLlamaCPPModelsAddsOnlyLoadedModels(t *testing.T) {
2323 }))
2424 defer server.Close()
2525
26- t.Setenv("ZOT_HOME", t.TempDir())
26+ t.Setenv("ZUT_HOME", t.TempDir())
2727 t.Setenv("LLAMA_BASE_URL", "")
2828 if err := AuthStoreFor().SetEndpointCredential(provider.LlamaCPPProviderID, server.URL, ""); err != nil {
2929 t.Fatal(err)
@@ -52,7 +52,7 @@ func TestRefreshLlamaCPPModelsAddsOnlyLoadedModels(t *testing.T) {
5252 // model to anthropic's default and persist.
5353 func TestValidateAndRepairConfig_MismatchedPair(t *testing.T) {
5454 home := t.TempDir()
55- t.Setenv("ZOT_HOME", home)
55+ t.Setenv("ZUT_HOME", home)
5656
5757 must := func(c Config) {
5858 t.Helper()
@@ -85,7 +85,7 @@ func TestValidateAndRepairConfig_MismatchedPair(t *testing.T) {
8585 // (e.g. user removed it from a previous build).
8686 func TestValidateAndRepairConfig_UnknownProvider(t *testing.T) {
8787 home := t.TempDir()
88- t.Setenv("ZOT_HOME", home)
88+ t.Setenv("ZUT_HOME", home)
8989
9090 b, _ := json.Marshal(Config{Provider: "made-up-provider", Model: "some-model"})
9191 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
@@ -106,7 +106,7 @@ func TestValidateAndRepairConfig_UnknownProvider(t *testing.T) {
106106 // longer in the catalog.
107107 func TestValidateAndRepairConfig_UnknownModel(t *testing.T) {
108108 home := t.TempDir()
109- t.Setenv("ZOT_HOME", home)
109+ t.Setenv("ZUT_HOME", home)
110110
111111 b, _ := json.Marshal(Config{Provider: "anthropic", Model: "claude-deleted-model"})
112112 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
@@ -124,7 +124,7 @@ func TestValidateAndRepairConfig_UnknownModel(t *testing.T) {
124124
125125 func TestValidateAndRepairConfig_DuplicateModelIDValidForConfiguredProvider(t *testing.T) {
126126 home := t.TempDir()
127- t.Setenv("ZOT_HOME", home)
127+ t.Setenv("ZUT_HOME", home)
128128
129129 b, _ := json.Marshal(Config{Provider: "openai-codex", Model: "gpt-5.5"})
130130 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
@@ -142,7 +142,7 @@ func TestValidateAndRepairConfig_DuplicateModelIDValidForConfiguredProvider(t *t
142142
143143 func TestValidateAndRepairConfig_OpenRouterPreservesRoutedModelID(t *testing.T) {
144144 home := t.TempDir()
145- t.Setenv("ZOT_HOME", home)
145+ t.Setenv("ZUT_HOME", home)
146146
147147 want := "deepseek/deepseek-v4-flash"
148148 b, _ := json.Marshal(Config{Provider: "openrouter", Model: want})
@@ -161,7 +161,7 @@ func TestValidateAndRepairConfig_OpenRouterPreservesRoutedModelID(t *testing.T)
161161
162162 func TestValidateAndRepairConfig_GatewayPlainUnknownModelStillRepairs(t *testing.T) {
163163 home := t.TempDir()
164- t.Setenv("ZOT_HOME", home)
164+ t.Setenv("ZUT_HOME", home)
165165
166166 b, _ := json.Marshal(Config{Provider: "openrouter", Model: "not-a-routed-model"})
167167 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
@@ -180,7 +180,7 @@ func TestValidateAndRepairConfig_GatewayPlainUnknownModelStillRepairs(t *testing
180180 // TestValidateAndRepairConfig_HappyPath leaves a valid config alone.
181181 func TestValidateAndRepairConfig_HappyPath(t *testing.T) {
182182 home := t.TempDir()
183- t.Setenv("ZOT_HOME", home)
183+ t.Setenv("ZUT_HOME", home)
184184
185185 b, _ := json.Marshal(Config{Provider: "anthropic", Model: "claude-sonnet-4-5"})
186186 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
M packages/agent/modes/alert_test.go +4 -4
@@ -8,10 +8,10 @@ import (
88 "testing"
99 "time"
1010
11- "github.com/patriceckhart/zot/packages/agent/extproto"
12- "github.com/patriceckhart/zot/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"
14- "github.com/patriceckhart/zot/packages/tui"
11+ "github.com/bnema/zut/packages/agent/extproto"
12+ "github.com/bnema/zut/packages/core"
13+ "github.com/bnema/zut/packages/provider"
14+ "github.com/bnema/zut/packages/tui"
1515 )
1616
1717 type alertTestTerminal struct {
M packages/agent/modes/auto_compact_test.go +1 -1
@@ -3,7 +3,7 @@ package modes
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/provider"
6+ "github.com/bnema/zut/packages/provider"
77 )
88
99 func TestNormalizeAutoCompactThreshold(t *testing.T) {
M packages/agent/modes/auto_subagents_policy_test.go +3 -3
@@ -6,9 +6,9 @@ import (
66 "testing"
77 "time"
88
9- "github.com/patriceckhart/zot/packages/agent/subagents"
10- "github.com/patriceckhart/zot/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/agent/subagents"
10+ "github.com/bnema/zut/packages/core"
11+ "github.com/bnema/zut/packages/provider"
1212 )
1313
1414 func TestAutoSubagentsSettingsDisableUnavailablePolicy(t *testing.T) {
M packages/agent/modes/auto_subagents_test.go +3 -3
@@ -8,9 +8,9 @@ import (
88 "testing"
99 "time"
1010
11- "github.com/patriceckhart/zot/packages/agent/subagents"
12- "github.com/patriceckhart/zot/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"
11+ "github.com/bnema/zut/packages/agent/subagents"
12+ "github.com/bnema/zut/packages/core"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 func TestAutoSubagentsSystemPromptTogglesProfileManifestWithDelegationGuidance(t *testing.T) {
M packages/agent/modes/bot/adapter.go +1 -1
@@ -7,7 +7,7 @@ package bot
77 import (
88 "context"
99
10- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 // InboundMessage is a protocol-normalised message from a user.
M packages/agent/modes/bot/runner.go +4 -4
@@ -8,8 +8,8 @@ import (
88 "strings"
99 "sync"
1010
11- "github.com/patriceckhart/zot/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"
11+ "github.com/bnema/zut/packages/core"
12+ "github.com/bnema/zut/packages/provider"
1313 )
1414
1515 // stderr is a tiny hook so tests can redirect bot logging.
@@ -17,7 +17,7 @@ var stderr = func() io.Writer { return os.Stderr }
1717
1818 // Config holds runner-level settings that are protocol-independent.
1919 type Config struct {
20- ZotHome string
20+ ZutHome string
2121 Provider string
2222 Model string
2323 AuthMethod string
@@ -107,7 +107,7 @@ func (r *Runner) handleCommand(cmd Command, msg InboundMessage) {
107107 switch cmd {
108108 case CmdStart, CmdHelp:
109109 _ = r.adapter.Send(context.Background(), msg.ChannelID,
110- "send me any message and i'll forward it to zot. attach an image and i'll pass it to the model. commands: /status, /stop, or plain stop.",
110+ "send me any message and i'll forward it to zut. attach an image and i'll pass it to the model. commands: /status, /stop, or plain stop.",
111111 SendOptions{ReplyToMessageID: msg.MessageID})
112112 case CmdStatus:
113113 r.sendStatus(msg.ChannelID, msg.MessageID)
M packages/agent/modes/bot/runner_test.go +2 -2
@@ -6,8 +6,8 @@ import (
66 "testing"
77 "time"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 type testAdapter struct{}
M packages/agent/modes/bot/status.go +1 -1
@@ -5,7 +5,7 @@ import (
55 "os"
66 "strings"
77
8- "github.com/patriceckhart/zot/packages/provider"
8+ "github.com/bnema/zut/packages/provider"
99 )
1010
1111 // StatusSnapshot is the small cross-host state bundle rendered for
M packages/agent/modes/btw_dialog.go +3 -3
@@ -6,9 +6,9 @@ import (
66 "strings"
77 "sync"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"
11- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/provider"
11+ "github.com/bnema/zut/packages/tui"
1212 )
1313
1414 // btwTurn is one user/assistant pair within a side chat. Kept
M packages/agent/modes/btw_dialog_test.go +3 -3
@@ -9,9 +9,9 @@ import (
99 "testing"
1010 "time"
1111
12- "github.com/patriceckhart/zot/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"
14- "github.com/patriceckhart/zot/packages/tui"
12+ "github.com/bnema/zut/packages/core"
13+ "github.com/bnema/zut/packages/provider"
14+ "github.com/bnema/zut/packages/tui"
1515 )
1616
1717 type btwToolTestClient struct {
M packages/agent/modes/changelog_dialog.go +3 -3
@@ -4,11 +4,11 @@ import (
44 "fmt"
55 "strings"
66
7- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/tui"
88 )
99
1010 // changelogDialog is the one-shot release-notes overlay shown the
11-// first time a user launches a new zot version. The body is the
11+// first time a user launches a new zut version. The body is the
1212 // markdown from the GitHub release page, rendered through the same
1313 // pipeline used for assistant messages so code fences + bold +
1414 // links look right.
@@ -76,7 +76,7 @@ func (d *changelogDialog) Render(th tui.Theme, width int) []string {
7676 if !d.Active() {
7777 return nil
7878 }
79- title := fmt.Sprintf("zot %s \u2014 release notes (any key to dismiss)", d.version)
79+ title := fmt.Sprintf("zut %s \u2014 release notes (any key to dismiss)", d.version)
8080 out := []string{frameHeaderColor(th, title, width, th.Accent)}
8181 if d.url != "" {
8282 out = append(out, " "+th.FGColor(th.Muted, d.url))
M packages/agent/modes/changelog_dialog_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "strings"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/tui"
88 )
99
1010 func TestChangelogDialogWrapsReleaseNotes(t *testing.T) {
M packages/agent/modes/clipboard_images.go +1 -1
@@ -3,7 +3,7 @@ package modes
33 import (
44 "strings"
55
6- "github.com/patriceckhart/zot/packages/provider"
6+ "github.com/bnema/zut/packages/provider"
77 )
88
99 type clipboardImageAttachment struct {
M packages/agent/modes/clipboard_images_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "strings"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/provider"
7+ "github.com/bnema/zut/packages/provider"
88 )
99
1010 func testClipboardImage(marker string, data string) clipboardImageAttachment {
M packages/agent/modes/clipboard_inputs_test.go +1 -1
@@ -3,7 +3,7 @@ package modes
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/tui"
6+ "github.com/bnema/zut/packages/tui"
77 )
88
99 func TestFilterInputsAcceptPaste(t *testing.T) {
M packages/agent/modes/clipboard_paste.go +1 -1
@@ -3,7 +3,7 @@ package modes
33 import (
44 "strings"
55
6- "github.com/patriceckhart/zot/packages/tui"
6+ "github.com/bnema/zut/packages/tui"
77 )
88
99 type clipboardTextReader func() (string, bool, error)
M packages/agent/modes/clipboard_paste_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "errors"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/tui"
88 )
99
1010 func TestResolveClipboardTextConvertsClipboardKeyToPaste(t *testing.T) {
M packages/agent/modes/compact_queue_test.go +3 -3
@@ -7,9 +7,9 @@ import (
77 "testing"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"
12- "github.com/patriceckhart/zot/packages/tui"
10+ "github.com/bnema/zut/packages/core"
11+ "github.com/bnema/zut/packages/provider"
12+ "github.com/bnema/zut/packages/tui"
1313 )
1414
1515 type compactQueueClient struct {
M packages/agent/modes/confirm_dialog.go +2 -2
@@ -6,8 +6,8 @@ import (
66 "sync"
77 "time"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 // confirmRequest is one pending confirmation. The agent goroutine
M packages/agent/modes/confirm_dialog_test.go +4 -4
@@ -8,10 +8,10 @@ import (
88 "testing"
99 "time"
1010
11- "github.com/patriceckhart/zot/packages/agent/extproto"
12- "github.com/patriceckhart/zot/packages/agent/tools"
13- "github.com/patriceckhart/zot/packages/core"
14- "github.com/patriceckhart/zot/packages/tui"
11+ "github.com/bnema/zut/packages/agent/extproto"
12+ "github.com/bnema/zut/packages/agent/tools"
13+ "github.com/bnema/zut/packages/core"
14+ "github.com/bnema/zut/packages/tui"
1515 )
1616
1717 func TestConfirmToolCallAttachesDiffBeforeDecision(t *testing.T) {
M packages/agent/modes/dialog_frame.go +1 -1
@@ -5,7 +5,7 @@ import (
55
66 "github.com/mattn/go-runewidth"
77
8- "github.com/patriceckhart/zot/packages/tui"
8+ "github.com/bnema/zut/packages/tui"
99 )
1010
1111 // frameHeader returns a full-width rule with a small title at the left.
M packages/agent/modes/dialog_text.go +1 -1
@@ -3,7 +3,7 @@ package modes
33 import (
44 "strings"
55
6- "github.com/patriceckhart/zot/packages/tui"
6+ "github.com/bnema/zut/packages/tui"
77 )
88
99 const dialogTextIndent = " "
M packages/agent/modes/dialog_text_test.go +2 -2
@@ -6,8 +6,8 @@ import (
66
77 "github.com/mattn/go-runewidth"
88
9- "github.com/patriceckhart/zot/packages/agent/subagents"
10- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/agent/subagents"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 func assertRowsFitWidth(t *testing.T, rows []string, width int) {
M packages/agent/modes/double_escape.go +3 -3
@@ -5,9 +5,9 @@ import (
55 "strings"
66 "time"
77
8- "github.com/patriceckhart/zot/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"
8+ "github.com/bnema/zut/packages/core"
9+ "github.com/bnema/zut/packages/provider"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 const sessionTreeEscapeWindow = 500 * time.Millisecond
M packages/agent/modes/double_escape_test.go +3 -3
@@ -5,9 +5,9 @@ import (
55 "testing"
66 "time"
77
8- "github.com/patriceckhart/zot/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"
8+ "github.com/bnema/zut/packages/core"
9+ "github.com/bnema/zut/packages/provider"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 func TestDoubleEscapeTrackerWindowAndReset(t *testing.T) {
M packages/agent/modes/ext_notes_test.go +1 -1
@@ -6,7 +6,7 @@ import (
66 "strings"
77 "testing"
88
9- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/tui"
1010 )
1111
1212 func newNotesTestInteractive() *Interactive {
M packages/agent/modes/ext_panel_dialog.go +1 -1
@@ -5,7 +5,7 @@ import (
55
66 "github.com/mattn/go-runewidth"
77
8- "github.com/patriceckhart/zot/packages/tui"
8+ "github.com/bnema/zut/packages/tui"
99 )
1010
1111 type extPanelDialog struct {
M packages/agent/modes/fast_mode_test.go +2 -2
@@ -6,8 +6,8 @@ import (
66 "strings"
77 "testing"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 type recordingFastModeSettingsStore struct {
M packages/agent/modes/file_suggest.go +4 -4
@@ -10,8 +10,8 @@ import (
1010
1111 "github.com/sahilm/fuzzy"
1212
13- "github.com/patriceckhart/zot/packages/ignore"
14- "github.com/patriceckhart/zot/packages/tui"
13+ "github.com/bnema/zut/packages/ignore"
14+ "github.com/bnema/zut/packages/tui"
1515 )
1616
1717 // recursiveScanLimits bound the recursive walk so the picker stays
@@ -63,7 +63,7 @@ type fileSuggester struct {
6363 // cachedMTime is the mtime of cachedDir at the time of the scan.
6464 // scan() compares the current mtime against this on every call and
6565 // re-reads the directory if it has changed, so files or folders
66- // created mid-session show up in the picker without restarting zot.
66+ // created mid-session show up in the picker without restarting zut.
6767 // Stat is cheap (single syscall) so doing it per keystroke while
6868 // the popup is open does not impact responsiveness.
6969 cachedMTime time.Time
@@ -129,7 +129,7 @@ func (s *fileSuggester) browseDir() string {
129129 // Results are cached by absolute path + mtime: a repeated call against
130130 // the same directory returns the cached slice when nothing has changed
131131 // on disk, and re-reads when an entry was added, removed, or renamed
132-// (any of which bumps the directory's mtime on every filesystem zot
132+// (any of which bumps the directory's mtime on every filesystem zut
133133 // supports). A failed stat falls through to a fresh ReadDir rather
134134 // than returning a stale cache so transient errors self-heal.
135135 func (s *fileSuggester) scan() []fileEntry {
M packages/agent/modes/help.go +2 -2
@@ -4,8 +4,8 @@ import (
44 "fmt"
55 "strings"
66
7+ "github.com/bnema/zut/packages/tui"
78 "github.com/mattn/go-runewidth"
8- "github.com/patriceckhart/zot/packages/tui"
99 )
1010
1111 // helpKeyRows is the list of keybindings shown by /help.
@@ -73,7 +73,7 @@ func renderHelpBlock(th tui.Theme, width int, llamaConfigured bool) []string {
7373 }
7474
7575 var out []string
76- out = append(out, frameHeader(th, "zot help", width), "")
76+ out = append(out, frameHeader(th, "zut help", width), "")
7777
7878 // commands section
7979 out = append(out, tui.Bold("slash commands:"))
M packages/agent/modes/help_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "strings"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/tui"
88 )
99
1010 func TestHelpShowsLlamaOnlyWhenConfigured(t *testing.T) {
M packages/agent/modes/input_history_test.go +3 -3
@@ -3,9 +3,9 @@ package modes
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/core"
7- "github.com/patriceckhart/zot/packages/provider"
8- "github.com/patriceckhart/zot/packages/tui"
6+ "github.com/bnema/zut/packages/core"
7+ "github.com/bnema/zut/packages/provider"
8+ "github.com/bnema/zut/packages/tui"
99 )
1010
1111 func TestInputHistoryUsesUpDown(t *testing.T) {
M packages/agent/modes/interactive.go +56 -56
@@ -13,16 +13,16 @@ import (
1313 "sync"
1414 "time"
1515
16- "github.com/patriceckhart/zot/packages/agent/extensions"
17- "github.com/patriceckhart/zot/packages/agent/extproto"
18- "github.com/patriceckhart/zot/packages/agent/modes/telegram"
19- "github.com/patriceckhart/zot/packages/agent/skills"
20- "github.com/patriceckhart/zot/packages/agent/subagents"
21- "github.com/patriceckhart/zot/packages/agent/tools"
22- "github.com/patriceckhart/zot/packages/core"
23- "github.com/patriceckhart/zot/packages/provider"
24- "github.com/patriceckhart/zot/packages/provider/auth"
25- "github.com/patriceckhart/zot/packages/tui"
16+ "github.com/bnema/zut/packages/agent/extensions"
17+ "github.com/bnema/zut/packages/agent/extproto"
18+ "github.com/bnema/zut/packages/agent/modes/telegram"
19+ "github.com/bnema/zut/packages/agent/skills"
20+ "github.com/bnema/zut/packages/agent/subagents"
21+ "github.com/bnema/zut/packages/agent/tools"
22+ "github.com/bnema/zut/packages/core"
23+ "github.com/bnema/zut/packages/provider"
24+ "github.com/bnema/zut/packages/provider/auth"
25+ "github.com/bnema/zut/packages/tui"
2626 )
2727
2828 // InteractiveConfig configures the interactive loop.
@@ -121,12 +121,12 @@ type InteractiveConfig struct {
121121
122122 // FlatTools renders tool calls without the bordered panel (a quiet
123123 // header line plus indented, frameless output). Mirrors the
124- // resolved tool_render config / ZOT_FLAT_TOOLS env at startup.
124+ // resolved tool_render config / ZUT_FLAT_TOOLS env at startup.
125125 FlatTools bool
126126
127127 // CompactUser renders sent user messages as a single quiet gutter
128128 // line instead of a padded, tinted bubble. Mirrors the resolved
129- // compact_input config / ZOT_COMPACT_INPUT env at startup.
129+ // compact_input config / ZUT_COMPACT_INPUT env at startup.
130130 CompactUser bool
131131
132132 // CompactMode mirrors the persisted compact_mode flag at startup.
@@ -163,7 +163,7 @@ type InteractiveConfig struct {
163163 SubagentsSystemAddendum string
164164 SettingsStore SettingsStore
165165
166- // Agent is optional. If nil, zot opens without credentials; the
166+ // Agent is optional. If nil, zut opens without credentials; the
167167 // user must /login before they can prompt.
168168 Agent *core.Agent
169169
@@ -209,8 +209,8 @@ type InteractiveConfig struct {
209209 // the concrete provider/model in use.
210210 BuildAgent func() (*core.Agent, string, string, error)
211211
212- // SetKimiCLIFallbackDisabled controls whether zot may fall back to
213- // the official Kimi Code CLI token when zot has no stored Kimi token.
212+ // SetKimiCLIFallbackDisabled controls whether zut may fall back to
213+ // the official Kimi Code CLI token when zut has no stored Kimi token.
214214 SetKimiCLIFallbackDisabled func(disabled bool) error
215215
216216 // BuildAgentFor rebuilds the agent with an explicit provider/model
@@ -232,13 +232,13 @@ type InteractiveConfig struct {
232232 // picker to only show reachable models.
233233 LoggedInProviders func() []string
234234
235- // ZotHome is zot's global state directory, used by authentication,
235+ // ZutHome is zut's global state directory, used by authentication,
236236 // themes, extensions, and other shared configuration.
237- ZotHome string
237+ ZutHome string
238238
239239 // SessionsRoot is the root passed to core session operations. It differs
240- // from ZotHome for Zotfile agents, whose sessions are isolated by agent
241- // name. Empty falls back to ZotHome for embedders and tests.
240+ // from ZutHome for Zutfile agents, whose sessions are isolated by agent
241+ // name. Empty falls back to ZutHome for embedders and tests.
242242 SessionsRoot string
243243
244244 // Version is the binary's current version (from main.version).
@@ -260,7 +260,7 @@ type InteractiveConfig struct {
260260 // callback returns the new agent message slice so the TUI can invalidate.
261261 LoadSession func(path string) error
262262
263- // ChangeCWD switches the running zot session's working directory
263+ // ChangeCWD switches the running zut session's working directory
264264 // to path. The host closes the current session, rebuilds the
265265 // agent so tools / AGENTS.md / sandbox bind to the new cwd, and
266266 // opens a fresh session there. Returns an error if path doesn't
@@ -690,7 +690,7 @@ type Interactive struct {
690690 // shell escape, updated via BashTool progress for live rendering.
691691 shellLive string
692692
693- // awaitingStartupPre is true while the zotfile entry.pre auto-submit
693+ // awaitingStartupPre is true while the zutfile entry.pre auto-submit
694694 // is in flight. When it clears, deferredInitialInput is applied.
695695 awaitingStartupPre bool
696696 deferredInitialInput string
@@ -870,7 +870,7 @@ func (i *Interactive) Run(ctx context.Context) error {
870870 // selection over the wheel-speed boost, so we no longer turn it
871871 // on automatically. Wheel events fall through to the terminal's
872872 // own scrollback handler.
873- // Keep zot on the terminal's main screen. We intentionally do not
873+ // Keep zut on the terminal's main screen. We intentionally do not
874874 // enter the alternate-screen buffer (CSI ?1049h). The renderer emits
875875 // chat as normal terminal flow/scrollback and redraws only the live
876876 // input/status block on normal typing.
@@ -939,7 +939,7 @@ func (i *Interactive) Run(ctx context.Context) error {
939939 // want to dismiss it (e.g. to check /help or /exit first).
940940 if i.agent == nil {
941941 i.statusErr = "not logged in. pick a login method below or press esc to dismiss."
942- i.dialog.Open(i.cfg.ZotHome)
942+ i.dialog.Open(i.cfg.ZutHome)
943943 }
944944
945945 // Input goroutine. Buffered generously so a drag-drop that the
@@ -1441,7 +1441,7 @@ func (i *Interactive) buildChatLocked(cols int) []string {
14411441 }
14421442
14431443 // Update-available banner: prepended above everything else so it's
1444- // the first thing the user sees when opening a new zot session.
1444+ // the first thing the user sees when opening a new zut session.
14451445 // Once rendered, it stays until the user updates to a newer
14461446 // version — we don't persist a "dismissed" flag because this is
14471447 // cheap and re-showing it is how most users remember to update.
@@ -1515,7 +1515,7 @@ func (i *Interactive) sessionsRoot() string {
15151515 if i.cfg.SessionsRoot != "" {
15161516 return i.cfg.SessionsRoot
15171517 }
1518- return i.cfg.ZotHome
1518+ return i.cfg.ZutHome
15191519 }
15201520
15211521 // lastCols returns the current terminal width in columns.
@@ -1765,7 +1765,7 @@ func (i *Interactive) redraw() {
17651765 }
17661766
17671767 // Busy prefix shown at the far left of the status bar. The
1768- // spinner glyph and its funny-line message share the `zot`
1768+ // spinner glyph and its funny-line message share the `zut`
17691769 // label colour (Theme.Assistant) so the whole "who's working"
17701770 // band reads at a glance. Elapsed time stays muted because it
17711771 // drifts every second and shouldn't grab focus.
@@ -2149,7 +2149,7 @@ func hasImageEscape(line string) bool {
21492149
21502150 // snapViewportStartToImageBlock treats inline images as atomic blocks for
21512151 // scrolling. Terminal image protocols draw from a single escape row into a
2152-// separate graphics layer; the following blank rows are only zot's reserved
2152+// separate graphics layer; the following blank rows are only zut's reserved
21532153 // footprint. If the viewport starts on one of those blank rows, there is no
21542154 // correct partial-image state to render. Snap back to the escape row instead
21552155 // so the image is either shown from its beginning or skipped entirely.
@@ -2857,7 +2857,7 @@ func (i *Interactive) handleKey(ctx context.Context, k tui.Key) (done bool) {
28572857 // ("be quiet" in a shell) rather than a deliberate
28582858 // decision to kill a multi-minute model call that's
28592859 // already cost tokens. Use esc to interrupt a turn; use
2860- // a deliberate double-ctrl+c to exit zot entirely. First
2860+ // a deliberate double-ctrl+c to exit zut entirely. First
28612861 // press arms the exit hint, second press within
28622862 // ctrlCExitWindow quits.
28632863 if i.busy {
@@ -4094,7 +4094,7 @@ func (i *Interactive) openSettingsDialog() {
40944094 if themeName == "" {
40954095 themeName = "auto"
40964096 }
4097- if themeName != "auto" && !tui.ThemeExists(i.cfg.ZotHome, themeName) {
4097+ if themeName != "auto" && !tui.ThemeExists(i.cfg.ZutHome, themeName) {
40984098 themeName = "auto"
40994099 i.cfg.ThemeName = ""
41004100 if i.cfg.SettingsStore != nil {
@@ -4104,7 +4104,7 @@ func (i *Interactive) openSettingsDialog() {
41044104 }
41054105 themeOptions := []settingsOption{}
41064106 themeChoice := 0
4107- availableThemes := tui.AvailableThemes(i.cfg.ZotHome)
4107+ availableThemes := tui.AvailableThemes(i.cfg.ZutHome)
41084108 if i.cfg.ExtensionThemes != nil {
41094109 availableThemes = append(availableThemes, i.cfg.ExtensionThemes()...)
41104110 }
@@ -4210,7 +4210,7 @@ func (i *Interactive) openSettingsDialog() {
42104210 {
42114211 key: "auto_compact_threshold",
42124212 label: "auto-compact threshold",
4213- desc: "choose how full the model context can get before zot condenses conversation history",
4213+ desc: "choose how full the model context can get before zut condenses conversation history",
42144214 options: autoCompactOptions,
42154215 choice: autoCompactChoice,
42164216 },
@@ -4809,7 +4809,7 @@ func (i *Interactive) applySettingToggle(key string, value bool) {
48094809 }
48104810 }
48114811 // Flip the live picker so the next @ reflects the new mode
4812- // without restarting zot. SetRecursive drops its cache.
4812+ // without restarting zut. SetRecursive drops its cache.
48134813 i.fileSuggest.SetRecursive(value)
48144814 i.mu.Lock()
48154815 i.statusOK = "recursive @-file search " + onOff(value)
@@ -4982,13 +4982,13 @@ func (i *Interactive) applyThemeNow(name string) {
49824982 // themes live; inherited mode must not issue OSC queries while the TUI
49834983 // already owns raw stdin.
49844984 detected.Terminal = i.cfg.Theme.Terminal
4985- th, applied, err := tui.LoadThemeFromHome(i.cfg.ZotHome, name, detected)
4985+ th, applied, err := tui.LoadThemeFromHome(i.cfg.ZutHome, name, detected)
49864986 if err != nil {
49874987 if i.cfg.SettingsStore != nil {
49884988 _ = i.cfg.SettingsStore.SetTheme("auto")
49894989 }
49904990 i.cfg.ThemeName = ""
4991- th, _, _ = tui.LoadThemeFromHome(i.cfg.ZotHome, "auto", detected)
4991+ th, _, _ = tui.LoadThemeFromHome(i.cfg.ZutHome, "auto", detected)
49924992 i.mu.Lock()
49934993 i.statusErr = "theme missing; reset to default"
49944994 i.mu.Unlock()
@@ -5334,7 +5334,7 @@ func (i *Interactive) runSlash(ctx context.Context, cmd string) (done bool) {
53345334 i.scrollOffset = 0
53355335 i.mu.Unlock()
53365336 case "/login":
5337- i.dialog.Open(i.cfg.ZotHome)
5337+ i.dialog.Open(i.cfg.ZutHome)
53385338 case "/logout":
53395339 if len(parts) >= 2 {
53405340 // Explicit target: /logout anthropic | openai | all
@@ -5431,7 +5431,7 @@ func (i *Interactive) runSlash(ctx context.Context, cmd string) (done bool) {
54315431 // Hidden command: switch the running session's cwd. Not in
54325432 // slash_suggest, not in /help. Used by the workspaces
54335433 // extension's panel-key Enter handler so picking a row
5434- // jumps zot into that directory without relaunching.
5434+ // jumps zut into that directory without relaunching.
54355435 //
54365436 // Recovers the raw argument (path) from the original cmd
54375437 // string rather than parts, so paths with spaces survive.
@@ -5727,11 +5727,11 @@ func (i *Interactive) doLogout(target string) {
57275727 }
57285728
57295729 func providerSetupInfo(provider string) (string, []string, bool) {
5730- const docsURL = "https://raw.githubusercontent.com/patriceckhart/zot/main/docs/providers.md"
5730+ const docsURL = "https://raw.githubusercontent.com/bnema/zut/main/docs/providers.md"
57315731 switch provider {
57325732 case "amazon-bedrock":
57335733 return "Amazon Bedrock setup", []string{
5734- "Amazon Bedrock uses AWS credentials instead of a generic zot API-key entry.",
5734+ "Amazon Bedrock uses AWS credentials instead of a generic zut API-key entry.",
57355735 "Configure an AWS profile, IAM keys, bearer token, or role-based credentials.",
57365736 "",
57375737 "For Bedrock API keys, set:",
@@ -6736,7 +6736,7 @@ func (i *Interactive) startTurnRequest(parent context.Context, prompt string, im
67366736 // "last frame had the previous turn's tool overlay" and
67376737 // "this frame had it cleared above". Without this, the guard
67386738 // reads delta = -(rows in cleared overlay) and decrements
6739- // scrollOffset, which on terminals that mirror zot's pane
6739+ // scrollOffset, which on terminals that mirror zut's pane
67406740 // scroll into the host scrollbar visibly yanks the viewport.
67416741 // See autofollow_shrink_test.go for the exact arithmetic.
67426742 i.prevChatLen = 0
@@ -6827,7 +6827,7 @@ func (i *Interactive) startTurnRequest(parent context.Context, prompt string, im
68276827 }
68286828 // Persist the assistant's reply (and every tool row before
68296829 // it) to the session file while the turn memory is hot.
6830- // Without this, WriteNewTranscript only fires at zot exit,
6830+ // Without this, WriteNewTranscript only fires at zut exit,
68316831 // meaning a crash or ungraceful kill drops the whole
68326832 // conversation. FlushSession is idempotent (it advances the
68336833 // baseline so subsequent flushes only write new rows).
@@ -7145,7 +7145,7 @@ func (i *Interactive) handleEvent(ev core.AgentEvent) {
71457145 if tc, ok := i.toolCalls[e.ID]; ok {
71467146 tc.RawJSONBuf += e.Delta
71477147 // Refresh the live path as soon as it parses; used in
7148- // the header (write /Users/pat/Desktop/demo.ts)
7148+ // the header (write /Users/example/Desktop/demo.ts)
71497149 // while the content is still streaming.
71507150 if p, pok, _ := tui.ExtractPartialStringField(tc.RawJSONBuf, "path"); pok {
71517151 tc.LivePath = p
@@ -7365,7 +7365,7 @@ func (i *Interactive) openTelegramDialog() {
73657365 items := i.telegramMenuItems()
73667366 if len(items) == 0 {
73677367 i.mu.Lock()
7368- i.statusErr = "telegram not configured. run `zot telegram-bot setup` first."
7368+ i.statusErr = "telegram not configured. run `zut telegram-bot setup` first."
73697369 i.mu.Unlock()
73707370 i.invalidate()
73717371 return
@@ -7378,7 +7378,7 @@ func (i *Interactive) openTelegramDialog() {
73787378 // bridge state. Returns empty when no bot.json exists so the
73797379 // caller can show a helpful status line instead of an empty menu.
73807380 func (i *Interactive) telegramMenuItems() []telegramItem {
7381- cfg, err := telegram.LoadConfig(i.cfg.ZotHome)
7381+ cfg, err := telegram.LoadConfig(i.cfg.ZutHome)
73827382 if err != nil || cfg.BotToken == "" {
73837383 return nil
73847384 }
@@ -7432,7 +7432,7 @@ func (i *Interactive) telegramConnect() {
74327432 i.invalidate()
74337433 return
74347434 }
7435- cfg, err := telegram.LoadConfig(i.cfg.ZotHome)
7435+ cfg, err := telegram.LoadConfig(i.cfg.ZutHome)
74367436 if err != nil {
74377437 i.mu.Lock()
74387438 i.statusErr = "telegram: " + err.Error()
@@ -7442,7 +7442,7 @@ func (i *Interactive) telegramConnect() {
74427442 }
74437443 if cfg.BotToken == "" {
74447444 i.mu.Lock()
7445- i.statusErr = "telegram: no bot token configured. run `zot telegram-bot setup` first."
7445+ i.statusErr = "telegram: no bot token configured. run `zut telegram-bot setup` first."
74467446 i.mu.Unlock()
74477447 i.invalidate()
74487448 return
@@ -7450,10 +7450,10 @@ func (i *Interactive) telegramConnect() {
74507450 // Refuse to start when a background daemon is already polling
74517451 // the same bot. Two concurrent long-poll consumers race each
74527452 // update and one always loses, so DMs get half-delivered. The
7453- // user can `zot telegram-bot stop` first, then /telegram connect.
7454- if pid, alive, _ := telegram.IsRunning(i.cfg.ZotHome); alive && pid > 0 {
7453+ // user can `zut telegram-bot stop` first, then /telegram connect.
7454+ if pid, alive, _ := telegram.IsRunning(i.cfg.ZutHome); alive && pid > 0 {
74557455 i.mu.Lock()
7456- i.statusErr = fmt.Sprintf("telegram: bot daemon already running (pid %d). stop it with `zot telegram-bot stop` first.", pid)
7456+ i.statusErr = fmt.Sprintf("telegram: bot daemon already running (pid %d). stop it with `zut telegram-bot stop` first.", pid)
74577457 i.mu.Unlock()
74587458 i.invalidate()
74597459 return
@@ -7462,7 +7462,7 @@ func (i *Interactive) telegramConnect() {
74627462 Client: telegram.NewClient(cfg.BotToken),
74637463 Config: cfg,
74647464 Save: func(next telegram.Config) error {
7465- return telegram.SaveConfig(i.cfg.ZotHome, next)
7465+ return telegram.SaveConfig(i.cfg.ZutHome, next)
74667466 },
74677467 Host: &telegramHost{iv: i},
74687468 }
@@ -7884,12 +7884,12 @@ func (i *Interactive) telegramStatus() {
78847884 } else {
78857885 msg += " - awaiting pairing"
78867886 }
7887- } else if pid, alive, _ := telegram.IsRunning(i.cfg.ZotHome); alive && pid > 0 {
7887+ } else if pid, alive, _ := telegram.IsRunning(i.cfg.ZutHome); alive && pid > 0 {
78887888 msg = fmt.Sprintf("telegram: background daemon running (pid %d) - /telegram connect won't work until you stop it", pid)
78897889 } else {
7890- cfg, _ := telegram.LoadConfig(i.cfg.ZotHome)
7890+ cfg, _ := telegram.LoadConfig(i.cfg.ZutHome)
78917891 if cfg.BotToken == "" {
7892- msg = "telegram: not configured. run `zot telegram-bot setup` first."
7892+ msg = "telegram: not configured. run `zut telegram-bot setup` first."
78937893 } else {
78947894 msg = "telegram: disconnected"
78957895 if cfg.BotUsername != "" {
@@ -7963,8 +7963,8 @@ func (h *telegramHost) Notify(level, message string) {
79637963 // transcript on fork; no parent/siblings on tree).
79647964 func (i *Interactive) openSessionOpsDialog() {
79657965 items := []sessionOpsItem{
7966- {label: "export", action: "export", hint: "write the current session to a .zotsession file"},
7967- {label: "import", action: "import", hint: "load a .zotsession file into this directory"},
7966+ {label: "export", action: "export", hint: "write the current session to a .zutsession file"},
7967+ {label: "import", action: "import", hint: "load a .zutsession file into this directory"},
79687968 {label: "fork", action: "fork", hint: "branch from a past user message into a new session"},
79697969 {label: "tree", action: "tree", hint: "switch between branches in this directory"},
79707970 }
@@ -8034,7 +8034,7 @@ func (i *Interactive) doSessionExport(dst string) {
80348034 i.invalidate()
80358035 }
80368036
8037-// doSessionImport copies the .zotsession file at src into the
8037+// doSessionImport copies the .zutsession file at src into the
80388038 // running cwd's sessions directory and loads it as the active
80398039 // session, same as `/sessions` -> pick. When src is empty we ask
80408040 // the user to pass a path (no usable default here).
@@ -8042,7 +8042,7 @@ func (i *Interactive) doSessionImport(src string) {
80428042 src = unquotePath(src)
80438043 if src == "" {
80448044 i.mu.Lock()
8045- i.statusErr = "import: pass a path — e.g. /session import ~/Downloads/work.zotsession"
8045+ i.statusErr = "import: pass a path — e.g. /session import ~/Downloads/work.zutsession"
80468046 i.mu.Unlock()
80478047 i.invalidate()
80488048 return
M packages/agent/modes/interactive_model_test.go +1 -1
@@ -6,7 +6,7 @@ import (
66 "strings"
77 "testing"
88
9- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/tui"
1010 )
1111
1212 func TestReasoningSelectorUsesCurrentModelLevels(t *testing.T) {
M packages/agent/modes/jail_setting_test.go +1 -1
@@ -3,7 +3,7 @@ package modes
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/agent/tools"
6+ "github.com/bnema/zut/packages/agent/tools"
77 )
88
99 func TestJailByDefaultSettingUpdatesLiveSandbox(t *testing.T) {
M packages/agent/modes/json.go +3 -3
@@ -6,8 +6,8 @@ import (
66 "fmt"
77 "io"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 // RunJSON runs the agent to completion, writing one JSON object per
@@ -35,7 +35,7 @@ func RunJSON(ctx context.Context, ag *core.Agent, prompt string, images []provid
3535
3636 // EventToJSON converts an AgentEvent to a JSON-friendly map. The on-wire
3737 // schema is deliberately simple and flat. Exported so the RPC mode can
38-// reuse the same serialisation as `zot --json`.
38+// reuse the same serialisation as `zut --json`.
3939 func EventToJSON(ev core.AgentEvent) map[string]any {
4040 m := map[string]any{"type": ev.Type()}
4141 switch e := ev.(type) {
M packages/agent/modes/jump_dialog.go +2 -2
@@ -4,8 +4,8 @@ import (
44 "fmt"
55 "strings"
66
7- "github.com/patriceckhart/zot/packages/provider"
8- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/provider"
8+ "github.com/bnema/zut/packages/tui"
99 )
1010
1111 // jumpTarget describes one "turn" in the current session — a user
M packages/agent/modes/llama_dialog.go +2 -2
@@ -8,9 +8,9 @@ import (
88 "sync"
99 "time"
1010
11+ "github.com/bnema/zut/packages/provider"
12+ "github.com/bnema/zut/packages/tui"
1113 "github.com/mattn/go-runewidth"
12- "github.com/patriceckhart/zot/packages/provider"
13- "github.com/patriceckhart/zot/packages/tui"
1414 )
1515
1616 type llamaDialogStep int
M packages/agent/modes/llama_dialog_test.go +2 -2
@@ -8,8 +8,8 @@ import (
88 "testing"
99 "time"
1010
11- "github.com/patriceckhart/zot/packages/provider"
12- "github.com/patriceckhart/zot/packages/tui"
11+ "github.com/bnema/zut/packages/provider"
12+ "github.com/bnema/zut/packages/tui"
1313 )
1414
1515 func TestLlamaDialogSearchesAsUserTypes(t *testing.T) {
M packages/agent/modes/login_dialog.go +6 -6
@@ -6,9 +6,9 @@ import (
66 "sort"
77 "strings"
88
9- "github.com/patriceckhart/zot/packages/provider"
10- "github.com/patriceckhart/zot/packages/provider/auth"
11- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/provider"
10+ "github.com/bnema/zut/packages/provider/auth"
11+ "github.com/bnema/zut/packages/tui"
1212 )
1313
1414 // loginStep is the current node in the login dialog state machine.
@@ -66,11 +66,11 @@ func (d *loginDialog) Active() bool { return d != nil && d.step != loginStepClos
6666
6767 // Open starts the dialog from scratch and captures the current
6868 // login status for each provider so the picker can show it.
69-// zotHome is the zot state directory ($ZOT_HOME); auth.json
69+// zutHome is the zut state directory ($ZUT_HOME); auth.json
7070 // lives inside it. Passing the path in (instead of importing
7171 // the agent package to call AuthPath()) avoids a cyclic import
7272 // between agent and agent/modes.
73-func (d *loginDialog) Open(zotHome string) {
73+func (d *loginDialog) Open(zutHome string) {
7474 d.step = loginStepMethod
7575 d.method = ""
7676 d.provider = ""
@@ -94,7 +94,7 @@ func (d *loginDialog) Open(zotHome string) {
9494 // provider as not-logged-in. The status line just won't show
9595 // anything useful in that case, which is fine — the user
9696 // was about to log in anyway.
97- path := filepath.Join(zotHome, "auth.json")
97+ path := filepath.Join(zutHome, "auth.json")
9898 if creds, err := auth.NewStore(path).Load(); err == nil {
9999 d.status["anthropic"] = creds.Method("anthropic")
100100 d.status["openai"] = ""
M packages/agent/modes/login_dialog_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "strings"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/tui"
88 )
99
1010 func TestLoginDialogProviderPickerFiltersLikeModelPicker(t *testing.T) {
M packages/agent/modes/logout_dialog.go +1 -1
@@ -1,7 +1,7 @@
11 package modes
22
33 import (
4- "github.com/patriceckhart/zot/packages/tui"
4+ "github.com/bnema/zut/packages/tui"
55 )
66
77 // logoutDialog is the picker shown when the user runs `/logout`
M packages/agent/modes/logout_dialog_test.go +1 -1
@@ -4,8 +4,8 @@ import (
44 "strings"
55 "testing"
66
7+ "github.com/bnema/zut/packages/tui"
78 "github.com/mattn/go-runewidth"
8- "github.com/patriceckhart/zot/packages/tui"
99 )
1010
1111 func TestLogoutDialogSelectionBackgroundSpansFullWidth(t *testing.T) {
M packages/agent/modes/model_dialog.go +2 -2
@@ -5,9 +5,9 @@ import (
55 "sort"
66 "strings"
77
8+ "github.com/bnema/zut/packages/provider"
9+ "github.com/bnema/zut/packages/tui"
810 "github.com/mattn/go-runewidth"
9- "github.com/patriceckhart/zot/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"
1111 )
1212
1313 // modelDialog is an inline picker for choosing the active model.
M packages/agent/modes/ponytail_setting_test.go +1 -1
@@ -5,7 +5,7 @@ import (
55 "strings"
66 "testing"
77
8- "github.com/patriceckhart/zot/packages/core"
8+ "github.com/bnema/zut/packages/core"
99 )
1010
1111 type recordingPonytailSettingsStore struct {
M packages/agent/modes/print.go +3 -3
@@ -1,4 +1,4 @@
1-// Package modes implements zot's three run modes: print, json, interactive.
1+// Package modes implements zut's three run modes: print, json, interactive.
22 package modes
33
44 import (
@@ -7,8 +7,8 @@ import (
77 "io"
88 "strings"
99
10- "github.com/patriceckhart/zot/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/core"
11+ "github.com/bnema/zut/packages/provider"
1212 )
1313
1414 // RunPrint runs the agent to completion and writes only the final
M packages/agent/modes/print_test.go +2 -2
@@ -5,8 +5,8 @@ import (
55 "context"
66 "testing"
77
8- "github.com/patriceckhart/zot/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"
8+ "github.com/bnema/zut/packages/core"
9+ "github.com/bnema/zut/packages/provider"
1010 )
1111
1212 type printTestClient struct{}
M packages/agent/modes/reload_ext.go +2 -2
@@ -6,8 +6,8 @@ import (
66 "strings"
77 "time"
88
9- "github.com/patriceckhart/zot/packages/agent/extensions"
10- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/agent/extensions"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 const reloadStatusDuration = 5 * time.Second
M packages/agent/modes/reload_ext_test.go +3 -3
@@ -5,9 +5,9 @@ import (
55 "strings"
66 "testing"
77
8- "github.com/patriceckhart/zot/packages/agent/extensions"
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/tui"
8+ "github.com/bnema/zut/packages/agent/extensions"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 func TestFormatReloadStatusIncludesErrorDetails(t *testing.T) {
M packages/agent/modes/rescue_dialog.go +2 -2
@@ -4,8 +4,8 @@ import (
44 "fmt"
55 "strings"
66
7- "github.com/patriceckhart/zot/packages/provider"
8- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/provider"
8+ "github.com/bnema/zut/packages/tui"
99 )
1010
1111 // rescueDialog offers a quick model swap when the active turn fails
M packages/agent/modes/session_agent_root_test.go +8 -8
@@ -4,12 +4,12 @@ import (
44 "context"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/core"
8- "github.com/patriceckhart/zot/packages/provider"
7+ "github.com/bnema/zut/packages/core"
8+ "github.com/bnema/zut/packages/provider"
99 )
1010
1111 func TestSessionsSlashUsesAgentSessionRoot(t *testing.T) {
12- zotHome := t.TempDir()
12+ zutHome := t.TempDir()
1313 agentRoot := t.TempDir()
1414 cwd := t.TempDir()
1515
@@ -28,7 +28,7 @@ func TestSessionsSlashUsesAgentSessionRoot(t *testing.T) {
2828 }
2929
3030 i := NewInteractive(InteractiveConfig{
31- ZotHome: zotHome,
31+ ZutHome: zutHome,
3232 SessionsRoot: agentRoot,
3333 CWD: cwd,
3434 })
@@ -46,9 +46,9 @@ func TestSessionsSlashUsesAgentSessionRoot(t *testing.T) {
4646 }
4747 }
4848
49-func TestSessionsRootDefaultsToZotHome(t *testing.T) {
50- i := &Interactive{cfg: InteractiveConfig{ZotHome: "/zot/home"}}
51- if got := i.sessionsRoot(); got != "/zot/home" {
52- t.Fatalf("sessions root = %q, want ZotHome fallback", got)
49+func TestSessionsRootDefaultsToZutHome(t *testing.T) {
50+ i := &Interactive{cfg: InteractiveConfig{ZutHome: "/zut/home"}}
51+ if got := i.sessionsRoot(); got != "/zut/home" {
52+ t.Fatalf("sessions root = %q, want ZutHome fallback", got)
5353 }
5454 }
M packages/agent/modes/session_dialog.go +2 -2
@@ -7,8 +7,8 @@ import (
77 "sync"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/core"
11- "github.com/patriceckhart/zot/packages/tui"
10+ "github.com/bnema/zut/packages/core"
11+ "github.com/bnema/zut/packages/tui"
1212 )
1313
1414 type sessionLoadEventKind uint8
M packages/agent/modes/session_dialog_test.go +3 -3
@@ -8,9 +8,9 @@ import (
88 "time"
99 "unicode"
1010
11- "github.com/patriceckhart/zot/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"
13- "github.com/patriceckhart/zot/packages/tui"
11+ "github.com/bnema/zut/packages/core"
12+ "github.com/bnema/zut/packages/provider"
13+ "github.com/bnema/zut/packages/tui"
1414 )
1515
1616 func TestFormatSessionRowPlainSanitizesControlBytes(t *testing.T) {
M packages/agent/modes/session_ops_dialog.go +2 -2
@@ -1,13 +1,13 @@
11 package modes
22
33 import (
4- "github.com/patriceckhart/zot/packages/tui"
4+ "github.com/bnema/zut/packages/tui"
55 )
66
77 // sessionOpsDialog is the picker shown when the user runs `/session`
88 // without an argument. Offers the two portable-file operations on
99 // the current conversation: export (write the in-memory transcript
10-// plus meta to a .zotsession file) and import (load a .zotsession
10+// plus meta to a .zutsession file) and import (load a .zutsession
1111 // from another machine and swap it in as the active session).
1212 //
1313 // Shape mirrors telegramDialog and logoutDialog: tiny list, arrow
M packages/agent/modes/session_title.go +4 -4
@@ -5,9 +5,9 @@ import (
55 "strings"
66 "time"
77
8- "github.com/patriceckhart/zot/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"
8+ "github.com/bnema/zut/packages/core"
9+ "github.com/bnema/zut/packages/provider"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 const sessionTitleGenerationTimeout = 10 * time.Second
@@ -126,7 +126,7 @@ func (i *Interactive) writeTerminalTitleLocked(subject string) {
126126 if subject != "" && !terminalTitleEnabled(i.cfg.TerminalTitleEnabled) {
127127 return
128128 }
129- display := "zot"
129+ display := "zut"
130130 if subject != "" {
131131 display += ": " + subject
132132 }
M packages/agent/modes/session_title_test.go +5 -5
@@ -8,8 +8,8 @@ import (
88 "testing"
99 "time"
1010
11- "github.com/patriceckhart/zot/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"
11+ "github.com/bnema/zut/packages/core"
12+ "github.com/bnema/zut/packages/provider"
1313 )
1414
1515 type interactiveTitleClient struct {
@@ -119,7 +119,7 @@ func TestFirstRealPromptSetsAndPersistsTerminalTitle(t *testing.T) {
119119 case <-time.After(time.Second):
120120 t.Fatal("title was not persisted")
121121 }
122- waitTerminalOutput(t, term, "\x1b]0;zot: Fix login retries\x07")
122+ waitTerminalOutput(t, term, "\x1b]0;zut: Fix login retries\x07")
123123 if got := len(ag.Messages()); got != 2 {
124124 t.Fatalf("transcript message count = %d, want user + assistant only", got)
125125 }
@@ -264,7 +264,7 @@ func TestDisablingTerminalTitlesRestoresNeutralTitle(t *testing.T) {
264264 i.applySettingToggle("terminal_title_enabled", false)
265265
266266 got := term.String()
267- if !strings.Contains(got, "\x1b]0;zot: Existing work\x07") || !strings.Contains(got, "\x1b]0;zot\x07") {
267+ if !strings.Contains(got, "\x1b]0;zut: Existing work\x07") || !strings.Contains(got, "\x1b]0;zut\x07") {
268268 t.Fatalf("terminal title output = %q, want existing and neutral title sequences", got)
269269 }
270270 }
@@ -283,7 +283,7 @@ func TestLoadedSessionTitleIsRestoredWithoutGeneration(t *testing.T) {
283283 })
284284 i.markInteractiveStarted()
285285
286- if got, want := term.String(), "\x1b]0;zot: Existing work\x07"; got != want {
286+ if got, want := term.String(), "\x1b]0;zut: Existing work\x07"; got != want {
287287 t.Fatalf("restored terminal title = %q, want %q", got, want)
288288 }
289289 i.mu.Lock()
M packages/agent/modes/session_tree_dialog.go +5 -5
@@ -8,9 +8,9 @@ import (
88
99 "github.com/mattn/go-runewidth"
1010
11- "github.com/patriceckhart/zot/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"
13- "github.com/patriceckhart/zot/packages/tui"
11+ "github.com/bnema/zut/packages/core"
12+ "github.com/bnema/zut/packages/provider"
13+ "github.com/bnema/zut/packages/tui"
1414 )
1515
1616 // sessionTreeBoundary describes what a tree row points at. Message rows are
@@ -738,7 +738,7 @@ func sessionTreeRoleLabel(role provider.Role) string {
738738 case provider.RoleUser:
739739 return "you"
740740 case provider.RoleAssistant:
741- return "zot"
741+ return "zut"
742742 case provider.RoleTool:
743743 return "tool"
744744 default:
@@ -822,7 +822,7 @@ func colorSessionTreeLine(th tui.Theme, line string) string {
822822 switch {
823823 case strings.HasPrefix(plain, "you:"):
824824 return th.FGColor(th.FG, line)
825- case strings.HasPrefix(plain, "zot:"):
825+ case strings.HasPrefix(plain, "zut:"):
826826 return th.FGColor(th.Muted, line)
827827 case strings.HasPrefix(plain, "tool:"):
828828 return th.FGColor(th.ToolOut, line)
M packages/agent/modes/session_tree_dialog_test.go +3 -3
@@ -6,9 +6,9 @@ import (
66 "testing"
77 "time"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"
11- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/provider"
11+ "github.com/bnema/zut/packages/tui"
1212 )
1313
1414 func TestSessionTreeTargetCarriesEffectiveBoundaryAndDraft(t *testing.T) {
M packages/agent/modes/settings_dialog.go +1 -1
@@ -5,7 +5,7 @@ import (
55
66 "github.com/mattn/go-runewidth"
77
8- "github.com/patriceckhart/zot/packages/tui"
8+ "github.com/bnema/zut/packages/tui"
99 )
1010
1111 type settingsDialog struct {
M packages/agent/modes/shell_escape_test.go +4 -4
@@ -7,15 +7,15 @@ import (
77 "testing"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/core"
10+ "github.com/bnema/zut/packages/core"
1111 )
1212
1313 func TestShellEscapeAddsOutputToAgentContext(t *testing.T) {
1414 agent := core.NewAgent(nil, "", "", nil)
1515 i := NewInteractive(InteractiveConfig{Agent: agent, CWD: t.TempDir()})
16- cmd := "printf zot-shell-context"
16+ cmd := "printf zut-shell-context"
1717 if runtime.GOOS == "windows" {
18- cmd = "echo zot-shell-context"
18+ cmd = "echo zut-shell-context"
1919 }
2020
2121 i.startShellEscape(context.Background(), cmd)
@@ -28,7 +28,7 @@ func TestShellEscapeAddsOutputToAgentContext(t *testing.T) {
2828 i.mu.Unlock()
2929 if len(messages) == 1 && !running {
3030 text := userMessageText(messages[0])
31- if !strings.Contains(text, "zot-shell-context") {
31+ if !strings.Contains(text, "zut-shell-context") {
3232 t.Fatalf("shell context = %q, want command output", text)
3333 }
3434 if messages[0].Meta[shellEscapeMetaKey] != "true" {
M packages/agent/modes/skill_command.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "fmt"
55 "strings"
66
7- "github.com/patriceckhart/zot/packages/agent/skills"
7+ "github.com/bnema/zut/packages/agent/skills"
88 )
99
1010 const skillCommandPrefix = "/skill:"
M packages/agent/modes/skill_command_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "strings"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/agent/skills"
7+ "github.com/bnema/zut/packages/agent/skills"
88 )
99
1010 func TestExpandSkillCommandForcesNamedSkill(t *testing.T) {
M packages/agent/modes/skills_dialog.go +3 -3
@@ -4,8 +4,8 @@ import (
44 "fmt"
55 "strings"
66
7- "github.com/patriceckhart/zot/packages/agent/skills"
8- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/agent/skills"
8+ "github.com/bnema/zut/packages/tui"
99 )
1010
1111 // skillsDialog lists every discovered skill and lets the user view
@@ -100,7 +100,7 @@ func (d *skillsDialog) Render(th tui.Theme, width int) []string {
100100 out := []string{frameHeader(th, "skills (enter to view, esc to close)", width)}
101101 if len(d.skills) == 0 {
102102 out = append(out, " "+th.FGColor(th.Muted, "no user skills loaded"))
103- out = append(out, " "+th.FGColor(th.Muted, "add SKILL.md under $ZOT_HOME/skills, .zot/skills, .claude/skills, or .agents/skills"))
103+ out = append(out, " "+th.FGColor(th.Muted, "add SKILL.md under $ZUT_HOME/skills, .zut/skills, .claude/skills, or .agents/skills"))
104104 out = append(out, frameRule(th, width))
105105 return out
106106 }
M packages/agent/modes/slash_case_test.go +2 -2
@@ -21,8 +21,8 @@ func TestRunSlashPreservesArgumentCase(t *testing.T) {
2121 return errors.New("stop after capture")
2222 },
2323 }}
24- i.runSlash(context.Background(), "/CD /Users/Pat/Mixed Case")
25- if got != "/Users/Pat/Mixed Case" {
24+ i.runSlash(context.Background(), "/CD /Users/Example/Mixed Case")
25+ if got != "/Users/Example/Mixed Case" {
2626 t.Fatalf("ChangeCWD path = %q, want original argument case", got)
2727 }
2828 }
M packages/agent/modes/slash_suggest.go +4 -4
@@ -6,8 +6,8 @@ import (
66
77 "github.com/mattn/go-runewidth"
88
9- "github.com/patriceckhart/zot/packages/agent/skills"
10- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/agent/skills"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 // slashCommand is one entry in the autocomplete popup. Header rows
@@ -67,7 +67,7 @@ var slashCatalog = []slashCommand{
6767 {Name: "/telegram", Desc: "connect, disconnect, or show status of the telegram bridge"},
6868 {Name: "/settings", Desc: "open settings"},
6969 {Name: "/clear", Desc: "clear the chat transcript"},
70- {Name: "/exit", Desc: "exit zot"},
70+ {Name: "/exit", Desc: "exit zut"},
7171 }
7272
7373 // slashSuggester renders the popup that appears when the editor starts
@@ -205,7 +205,7 @@ func (s *slashSuggester) baseCatalog() []slashCommand {
205205
206206 // looksLikeSlashCommand reports whether text is an attempt at a slash
207207 // command (valid or not). Returns true for things like "/foo" or
208-// "/bar baz" but false for paths ("/Users/pat/...") and regexes
208+// "/bar baz" but false for paths ("/Users/example/...") and regexes
209209 // ("/foo.bar/") so those can be sent to the model as-is.
210210 //
211211 // The head after "/" must be a single simple word: only letters,
M packages/agent/modes/slash_suggest_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "slices"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/agent/skills"
7+ "github.com/bnema/zut/packages/agent/skills"
88 )
99
1010 func TestSlashSuggesterHidesUnjailUntilJailed(t *testing.T) {
M packages/agent/modes/spinner.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "math/rand"
55 "time"
66
7- "github.com/patriceckhart/zot/packages/tui"
7+ "github.com/bnema/zut/packages/tui"
88 )
99
1010 // spinner drives the busy animation shown in the status bar while a
M packages/agent/modes/startup_instructions_test.go +2 -2
@@ -4,7 +4,7 @@ import "testing"
44
55 func TestStartupResourcesAreOptIn(t *testing.T) {
66 cfg := InteractiveConfig{
7- StartupAgentName: "zot-maintenance",
7+ StartupAgentName: "zut-maintenance",
88 StartupContextPaths: []string{"/repo/AGENTS.md"},
99 StartupExtensionNames: []string{"workspaces"},
1010 StartupSkillNames: []string{"review"},
@@ -30,7 +30,7 @@ func assertStartupResourceCounts(t *testing.T, i *Interactive, want int) {
3030 t.Helper()
3131 wantAgent := ""
3232 if want > 0 {
33- wantAgent = "zot-maintenance"
33+ wantAgent = "zut-maintenance"
3434 }
3535 if got := i.view.StartupAgentName; got != wantAgent {
3636 t.Fatalf("startup agent = %q, want %q", got, wantAgent)
M packages/agent/modes/startup_pre_test.go +8 -8
@@ -7,7 +7,7 @@ import (
77 "testing"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/core"
10+ "github.com/bnema/zut/packages/core"
1111 )
1212
1313 func applyStartupPreResultForTest(t *testing.T, i *Interactive) {
@@ -66,9 +66,9 @@ func TestStartupPreShellWithoutAgentThenPrefill(t *testing.T) {
6666 i.deferredInitialInput = "after-pre"
6767 i.autoSubmitDeferred = false
6868
69- cmd := "printf zot-pre"
69+ cmd := "printf zut-pre"
7070 if runtime.GOOS == "windows" {
71- cmd = "echo zot-pre"
71+ cmd = "echo zut-pre"
7272 }
7373 i.startShellEscape(context.Background(), cmd)
7474
@@ -99,15 +99,15 @@ func TestStartupPreShellThenAutoSubmitShell(t *testing.T) {
9999 i := NewInteractive(InteractiveConfig{Agent: agent, CWD: t.TempDir()})
100100 i.runCtx = context.Background()
101101 i.awaitingStartupPre = true
102- i.deferredInitialInput = "!printf zot-default"
102+ i.deferredInitialInput = "!printf zut-default"
103103 if runtime.GOOS == "windows" {
104- i.deferredInitialInput = "!echo zot-default"
104+ i.deferredInitialInput = "!echo zut-default"
105105 }
106106 i.autoSubmitDeferred = true
107107
108- cmd := "printf zot-pre"
108+ cmd := "printf zut-pre"
109109 if runtime.GOOS == "windows" {
110- cmd = "echo zot-pre"
110+ cmd = "echo zut-pre"
111111 }
112112 i.startShellEscape(context.Background(), cmd)
113113
@@ -125,7 +125,7 @@ func TestStartupPreShellThenAutoSubmitShell(t *testing.T) {
125125 i.mu.Unlock()
126126 if len(messages) >= 2 && !running && !awaiting {
127127 joined := userMessageText(messages[0]) + "\n" + userMessageText(messages[1])
128- if !strings.Contains(joined, "zot-pre") || !strings.Contains(joined, "zot-default") {
128+ if !strings.Contains(joined, "zut-pre") || !strings.Contains(joined, "zut-default") {
129129 t.Fatalf("context = %q, want both pre and default shell output", joined)
130130 }
131131 return
M packages/agent/modes/stream.go +2 -2
@@ -7,8 +7,8 @@ import (
77 "os"
88 "strings"
99
10- "github.com/patriceckhart/zot/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/core"
11+ "github.com/bnema/zut/packages/provider"
1212 )
1313
1414 // RunStream runs the agent and writes assistant text to out as it
M packages/agent/modes/stream_test.go +2 -2
@@ -5,8 +5,8 @@ import (
55 "context"
66 "testing"
77
8- "github.com/patriceckhart/zot/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"
8+ "github.com/bnema/zut/packages/core"
9+ "github.com/bnema/zut/packages/provider"
1010 )
1111
1212 type streamTestClient struct {
M packages/agent/modes/subagents_dialog.go +4 -4
@@ -6,8 +6,8 @@ import (
66 "strings"
77 "time"
88
9- "github.com/patriceckhart/zot/packages/agent/subagents"
10- "github.com/patriceckhart/zot/packages/tui"
9+ "github.com/bnema/zut/packages/agent/subagents"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 // subagentsDialog is the dashboard shown by /subagents (no argument) and
@@ -132,10 +132,10 @@ func (d *subagentsDialog) editorPrompt(th tui.Theme) string {
132132
133133 // SetCurrentModel pins the model + provider every fresh spawn will
134134 // inherit. The host wires this to the same Model / Provider the rest
135-// of zot is currently using, so agents started from the dashboard
135+// of zut is currently using, so agents started from the dashboard
136136 // run on whatever the user last picked via /model. Pass empty
137137 // strings to clear the override (the child then resolves its own
138-// default the same way a bare `zot` invocation does).
138+// default the same way a bare `zut` invocation does).
139139 func (d *subagentsDialog) SetCurrentModel(model, providerID string) {
140140 d.pendingModel = model
141141 d.pendingProvider = providerID
M packages/agent/modes/subagents_dialog_test.go +3 -3
@@ -9,9 +9,9 @@ import (
99 "testing"
1010 "time"
1111
12- "github.com/patriceckhart/zot/packages/agent/subagents"
13- "github.com/patriceckhart/zot/packages/provider"
14- "github.com/patriceckhart/zot/packages/tui"
12+ "github.com/bnema/zut/packages/agent/subagents"
13+ "github.com/bnema/zut/packages/provider"
14+ "github.com/bnema/zut/packages/tui"
1515 )
1616
1717 // staticSnapshots returns a deterministic snapshot slice for the
M packages/agent/modes/subagents_slash.go +3 -3
@@ -5,9 +5,9 @@ import (
55 "fmt"
66 "strings"
77
8- "github.com/patriceckhart/zot/packages/agent/subagents"
9- "github.com/patriceckhart/zot/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"
8+ "github.com/bnema/zut/packages/agent/subagents"
9+ "github.com/bnema/zut/packages/provider"
10+ "github.com/bnema/zut/packages/tui"
1111 )
1212
1313 // runSubagents dispatches /subagents subcommands. Layout:
M packages/agent/modes/subagents_slash_test.go +1 -1
@@ -7,7 +7,7 @@ import (
77 "testing"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/agent/subagents"
10+ "github.com/bnema/zut/packages/agent/subagents"
1111 )
1212
1313 // newInteractiveForSupervisorTest builds the minimal Interactive scaffolding
M packages/agent/modes/telegram/adapter.go +4 -4
@@ -7,8 +7,8 @@ import (
77 "strings"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/agent/modes/bot"
11- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/agent/modes/bot"
11+ "github.com/bnema/zut/packages/provider"
1212 )
1313
1414 // Adapter implements bot.BotAdapter for Telegram.
@@ -31,7 +31,7 @@ func (a *Adapter) Run(ctx context.Context,
3131 commandHandler func(bot.Command, bot.InboundMessage),
3232 ) error {
3333 if a.Cfg.BotToken == "" {
34- return fmt.Errorf("no bot token configured; run `zot bot setup` first")
34+ return fmt.Errorf("no bot token configured; run `zut bot setup` first")
3535 }
3636 me, err := a.Client.GetMe(ctx)
3737 if err != nil {
@@ -121,7 +121,7 @@ func (a *Adapter) handleUpdate(ctx context.Context, u Update,
121121 a.Cfg.AllowedUserID = msg.From.ID
122122 _ = a.Save(*a.Cfg)
123123 _ = a.Client.SendMessage(ctx, msg.Chat.ID,
124- fmt.Sprintf("paired with @%s. send any message and i'll forward it to zot.", msg.From.Username),
124+ fmt.Sprintf("paired with @%s. send any message and i'll forward it to zut.", msg.From.Username),
125125 msg.MessageID)
126126 return
127127 }
M packages/agent/modes/telegram/api_test.go +7 -7
@@ -12,8 +12,8 @@ import (
1212 "testing"
1313 "time"
1414
15- "github.com/patriceckhart/zot/packages/agent/modes/bot"
16- "github.com/patriceckhart/zot/packages/provider"
15+ "github.com/bnema/zut/packages/agent/modes/bot"
16+ "github.com/bnema/zut/packages/provider"
1717 )
1818
1919 type roundTripFunc func(*http.Request) (*http.Response, error)
@@ -111,7 +111,7 @@ func TestAdapterRunDeletesWebhookBeforePolling(t *testing.T) {
111111 mu.Unlock()
112112 switch endpoint {
113113 case "getMe":
114- return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zot_test"}}`), nil
114+ return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zut_test"}}`), nil
115115 case "deleteWebhook":
116116 return telegramResponse(`{"ok":true,"result":true}`), nil
117117 case "getUpdates":
@@ -122,7 +122,7 @@ func TestAdapterRunDeletesWebhookBeforePolling(t *testing.T) {
122122 return nil, errors.New("unreachable")
123123 }
124124 }))
125- cfg := Config{BotToken: "test", BotID: 1, BotUsername: "zot_test"}
125+ cfg := Config{BotToken: "test", BotID: 1, BotUsername: "zut_test"}
126126 adapter := NewAdapter(client, &cfg, func(Config) error { return nil })
127127
128128 err := adapter.Run(ctx, func(bot.InboundMessage) {}, func(bot.Command, bot.InboundMessage) {})
@@ -151,7 +151,7 @@ func TestBridgeStartDeletesWebhookBeforePolling(t *testing.T) {
151151 mu.Unlock()
152152 switch endpoint {
153153 case "getMe":
154- return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zot_test"}}`), nil
154+ return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zut_test"}}`), nil
155155 case "deleteWebhook":
156156 return telegramResponse(`{"ok":true,"result":true}`), nil
157157 case "getUpdates":
@@ -165,7 +165,7 @@ func TestBridgeStartDeletesWebhookBeforePolling(t *testing.T) {
165165 }))
166166 bridge := &Bridge{
167167 Client: client,
168- Config: Config{BotToken: "test", BotID: 1, BotUsername: "zot_test"},
168+ Config: Config{BotToken: "test", BotID: 1, BotUsername: "zut_test"},
169169 Save: func(Config) error { return nil },
170170 Host: testBridgeHost{},
171171 }
@@ -201,7 +201,7 @@ func TestAdapterRunFailsBeforePollingWhenWebhookRemovalFails(t *testing.T) {
201201 endpoint := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]
202202 calls.WriteString(endpoint + "\n")
203203 if endpoint == "getMe" {
204- return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zot_test"}}`), nil
204+ return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zut_test"}}`), nil
205205 }
206206 return telegramResponse(`{"ok":false,"description":"webhook removal denied","result":false}`), nil
207207 }))
M packages/agent/modes/telegram/bridge.go +14 -14
@@ -7,8 +7,8 @@ import (
77 "sync"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/agent/modes/bot"
11- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/agent/modes/bot"
11+ "github.com/bnema/zut/packages/provider"
1212 )
1313
1414 // Host is the small interface the Bridge calls back into the TUI
@@ -37,7 +37,7 @@ type Host interface {
3737 // Host's running agent, then mirrors the agent's final assistant
3838 // text back to the paired Telegram user. One bridge per Interactive
3939 // instance; created on /telegram connect, stopped on /telegram
40-// disconnect or zot exit.
40+// disconnect or zut exit.
4141 type Bridge struct {
4242 Client *Client
4343 Config Config
@@ -52,10 +52,10 @@ type Bridge struct {
5252 workingCancel context.CancelFunc
5353
5454 // nextReplyFromTelegram is set when the next assistant reply
55- // should be sent bare (no "zot: " prefix) because the turn was
55+ // should be sent bare (no "zut: " prefix) because the turn was
5656 // initiated by a Telegram DM. The flag clears as soon as the
5757 // reply is flushed. TUI-originated turns leave the flag false
58- // so the reply is tagged "zot: " for clarity on the two-sided
58+ // so the reply is tagged "zut: " for clarity on the two-sided
5959 // transcript.
6060 nextReplyFromTelegram bool
6161 }
@@ -63,7 +63,7 @@ type Bridge struct {
6363 // State is the snapshot /telegram status reports.
6464 type State struct {
6565 Running bool
66- Username string // bot username, e.g. "zotbot"
66+ Username string // bot username, e.g. "zutbot"
6767 PairedID int64 // 0 when no user has claimed the bot yet
6868 }
6969
@@ -103,7 +103,7 @@ func (b *Bridge) Start(parent context.Context) error {
103103 }
104104 if b.Config.BotToken == "" {
105105 b.mu.Unlock()
106- return fmt.Errorf("no bot token configured; run `zot telegram-bot setup` first")
106+ return fmt.Errorf("no bot token configured; run `zut telegram-bot setup` first")
107107 }
108108 b.mu.Unlock()
109109
@@ -160,14 +160,14 @@ func (b *Bridge) Stop() {
160160 // OnAssistantText should be called by the TUI with the assistant's
161161 // final visible text for each turn. The bridge forwards it to the
162162 // paired chat in message-sized chunks. Prefix depends on which
163-// side initiated the turn: TUI-originated turns get "zot: " so the
164-// two-sided transcript reads naturally ("you: ..." / "zot: ..."),
163+// side initiated the turn: TUI-originated turns get "zut: " so the
164+// two-sided transcript reads naturally ("you: ..." / "zut: ..."),
165165 // while Telegram-originated turns send bare text (the user's own
166-// bubble is already on-screen, a "zot: " prefix would just add
166+// bubble is already on-screen, a "zut: " prefix would just add
167167 // visual noise to a plain back-and-forth).
168168 func (b *Bridge) OnAssistantText(text string) {
169169 b.mu.Lock()
170- // prefix := "zot: "
170+ // prefix := "zut: "
171171 prefix := ""
172172 workingCancel := b.workingCancel
173173 b.workingCancel = nil
@@ -182,7 +182,7 @@ func (b *Bridge) OnAssistantText(text string) {
182182 b.sendToPaired(text, prefix)
183183 }
184184
185-// OnUserTyped mirrors a message the user typed in the zot TUI into
185+// OnUserTyped mirrors a message the user typed in the zut TUI into
186186 // the paired Telegram chat, tagged "you:" so the Telegram thread
187187 // stays a complete record of the conversation (both TUI-originated
188188 // and Telegram-originated turns). Messages sent from Telegram
@@ -362,7 +362,7 @@ func (b *Bridge) handleUpdate(ctx context.Context, u Update) {
362362 _ = b.Save(b.Config)
363363 b.mu.Unlock()
364364 _ = b.Client.SendMessage(ctx, msg.Chat.ID,
365- fmt.Sprintf("paired with @%s. messages you send here now mirror into the zot tui.", msg.From.Username),
365+ fmt.Sprintf("paired with @%s. messages you send here now mirror into the zut tui.", msg.From.Username),
366366 msg.MessageID)
367367 b.Host.Notify("success", fmt.Sprintf("telegram paired with user %d (@%s)", msg.From.ID, msg.From.Username))
368368 return
@@ -391,7 +391,7 @@ func (b *Bridge) handleUpdate(ctx context.Context, u Update) {
391391 switch command {
392392 case bot.CmdStart, bot.CmdHelp:
393393 _ = b.Client.SendMessage(ctx, msg.Chat.ID,
394- "mirror is active. send me a message and it'll be forwarded to the zot tui. commands: /status, /stop, or plain stop.",
394+ "mirror is active. send me a message and it'll be forwarded to the zut tui. commands: /status, /stop, or plain stop.",
395395 msg.MessageID)
396396 case bot.CmdStatus:
397397 _ = b.Client.SendMessage(ctx, msg.Chat.ID, b.Host.Status(), msg.MessageID)
M packages/agent/modes/telegram/commands.go +1 -1
@@ -1,6 +1,6 @@
11 package telegram
22
3-import "github.com/patriceckhart/zot/packages/agent/modes/bot"
3+import "github.com/bnema/zut/packages/agent/modes/bot"
44
55 // isStopCommand is a shim to bot.IsStopCommand for backward compatibility.
66 var isStopCommand = bot.IsStopCommand
M packages/agent/modes/telegram/config.go +9 -9
@@ -1,4 +1,4 @@
1-// Package telegram implements zot's Telegram bot bridge.
1+// Package telegram implements zut's Telegram bot bridge.
22 //
33 // It runs in-process, polling Telegram for DMs and forwarding them to
44 // a core.Agent. Responses stream back as Telegram messages. Images
@@ -6,7 +6,7 @@
66 // provider.ImageBlock, so vision-capable models see them the same way
77 // they would via drag-and-drop in the TUI.
88 //
9-// State (bot token, allowed user id) lives in $ZOT_HOME/bot.json.
9+// State (bot token, allowed user id) lives in $ZUT_HOME/bot.json.
1010 package telegram
1111
1212 import (
@@ -26,14 +26,14 @@ type Config struct {
2626 }
2727
2828 // ConfigPath returns the path to bot.json.
29-func ConfigPath(zotHome string) string {
30- return filepath.Join(zotHome, "bot.json")
29+func ConfigPath(zutHome string) string {
30+ return filepath.Join(zutHome, "bot.json")
3131 }
3232
3333 // LoadConfig reads bot.json, returning a zero Config if it doesn't exist.
34-func LoadConfig(zotHome string) (Config, error) {
34+func LoadConfig(zutHome string) (Config, error) {
3535 var c Config
36- b, err := os.ReadFile(ConfigPath(zotHome))
36+ b, err := os.ReadFile(ConfigPath(zutHome))
3737 if errors.Is(err, os.ErrNotExist) {
3838 return c, nil
3939 }
@@ -47,15 +47,15 @@ func LoadConfig(zotHome string) (Config, error) {
4747 }
4848
4949 // SaveConfig writes bot.json atomically.
50-func SaveConfig(zotHome string, c Config) error {
51- if err := os.MkdirAll(zotHome, 0o755); err != nil {
50+func SaveConfig(zutHome string, c Config) error {
51+ if err := os.MkdirAll(zutHome, 0o755); err != nil {
5252 return err
5353 }
5454 b, err := json.MarshalIndent(c, "", " ")
5555 if err != nil {
5656 return err
5757 }
58- path := ConfigPath(zotHome)
58+ path := ConfigPath(zutHome)
5959 tmp := path + ".tmp"
6060 if err := os.WriteFile(tmp, b, 0o600); err != nil {
6161 return err
M packages/agent/modes/telegram/daemon.go +8 -8
@@ -4,30 +4,30 @@ import (
44 "path/filepath"
55 "time"
66
7- "github.com/patriceckhart/zot/packages/agent/modes/bot"
7+ "github.com/bnema/zut/packages/agent/modes/bot"
88 )
99
1010 // PIDPath returns the location of the bot's pid file.
11-func PIDPath(zotHome string) string { return filepath.Join(zotHome, "bot.pid") }
11+func PIDPath(zutHome string) string { return filepath.Join(zutHome, "bot.pid") }
1212
1313 // LogPath returns the location of the bot's log file (stdout+stderr
14-// from a detached `zot bot start`).
15-func LogPath(zotHome string) string { return filepath.Join(zotHome, "logs", "bot.log") }
14+// from a detached `zut bot start`).
15+func LogPath(zutHome string) string { return filepath.Join(zutHome, "logs", "bot.log") }
1616
1717 // WritePID persists pid to bot.pid. Overwrites any existing file.
18-func WritePID(zotHome string, pid int) error { return bot.WritePIDFile(PIDPath(zotHome), pid) }
18+func WritePID(zutHome string, pid int) error { return bot.WritePIDFile(PIDPath(zutHome), pid) }
1919
2020 // ReadPID returns the pid stored in bot.pid, or 0 if the file doesn't
2121 // exist. Returns an error for any other read/parse failure.
22-func ReadPID(zotHome string) (int, error) { return bot.ReadPIDFile(PIDPath(zotHome)) }
22+func ReadPID(zutHome string) (int, error) { return bot.ReadPIDFile(PIDPath(zutHome)) }
2323
2424 // RemovePID deletes the pid file if it exists.
25-func RemovePID(zotHome string) error { return bot.RemovePIDFile(PIDPath(zotHome)) }
25+func RemovePID(zutHome string) error { return bot.RemovePIDFile(PIDPath(zutHome)) }
2626
2727 // IsRunning returns (pid, true) if a live process with the recorded
2828 // pid exists, or (pid, false) if the pid file points to a dead process.
2929 // Stale pid files are left in place; the caller may remove them.
30-func IsRunning(zotHome string) (int, bool, error) { return bot.IsRunningAt(PIDPath(zotHome)) }
30+func IsRunning(zutHome string) (int, bool, error) { return bot.IsRunningAt(PIDPath(zutHome)) }
3131
3232 // StopProcess asks pid to exit and waits up to graceful for it to stop,
3333 // then escalates to a forced kill. Returns nil if the process is gone.
M packages/agent/modes/telegram/status.go +1 -1
@@ -1,6 +1,6 @@
11 package telegram
22
3-import "github.com/patriceckhart/zot/packages/agent/modes/bot"
3+import "github.com/bnema/zut/packages/agent/modes/bot"
44
55 // StatusSnapshot is an alias for bot.StatusSnapshot for backward compatibility.
66 type StatusSnapshot = bot.StatusSnapshot
M packages/agent/modes/telegram/status_test.go +3 -3
@@ -4,14 +4,14 @@ import (
44 "strings"
55 "testing"
66
7- "github.com/patriceckhart/zot/packages/provider"
7+ "github.com/bnema/zut/packages/provider"
88 )
99
1010 func TestFormatStatusIncludesModelUsageContextAndCWD(t *testing.T) {
1111 got := FormatStatus(StatusSnapshot{
1212 Provider: "openai",
1313 Model: "gpt-5.5",
14- CWD: "/tmp/zot",
14+ CWD: "/tmp/zut",
1515 Usage: provider.Usage{InputTokens: 961_000, OutputTokens: 10_000, CacheReadTokens: 770_000, CostUSD: 2.749},
1616 Subscription: true,
1717 ContextUsed: 44_800,
@@ -29,7 +29,7 @@ func TestFormatStatusIncludesModelUsageContextAndCWD(t *testing.T) {
2929 "11.2%/400k",
3030 "state: working",
3131 "queued: 2",
32- "cwd: /tmp/zot",
32+ "cwd: /tmp/zut",
3333 }
3434 for _, want := range wants {
3535 if !strings.Contains(got, want) {
M packages/agent/modes/telegram_dialog.go +1 -1
@@ -1,7 +1,7 @@
11 package modes
22
33 import (
4- "github.com/patriceckhart/zot/packages/tui"
4+ "github.com/bnema/zut/packages/tui"
55 )
66
77 // telegramDialog is the picker shown when the user runs `/telegram`
M packages/agent/modes/theme_setting_test.go +1 -1
@@ -3,7 +3,7 @@ package modes
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/tui"
6+ "github.com/bnema/zut/packages/tui"
77 )
88
99 func TestSettingsDialogOffersInheritedTheme(t *testing.T) {
M packages/agent/modes/update_banner.go +3 -3
@@ -3,7 +3,7 @@ package modes
33 import (
44 "fmt"
55
6- "github.com/patriceckhart/zot/packages/tui"
6+ "github.com/bnema/zut/packages/tui"
77 )
88
99 // UpdateInfo mirrors agent.UpdateInfo without the import cycle. The
@@ -32,10 +32,10 @@ func renderUpdateBanner(th tui.Theme, info UpdateInfo, width int) []string {
3232 }
3333 out = append(out, "")
3434
35- title := fmt.Sprintf("zot %s is available (you're on %s).", info.Latest, info.Current)
35+ title := fmt.Sprintf("zut %s is available (you're on %s).", info.Latest, info.Current)
3636 out = append(out, " "+th.FGColor(color, tui.Bold(title)))
3737 out = append(out, "")
38- out = append(out, " "+th.FGColor(th.Muted, "run: ")+th.FGColor(color, "zot update"))
38+ out = append(out, " "+th.FGColor(th.Muted, "run: ")+th.FGColor(color, "zut update"))
3939
4040 if info.URL != "" {
4141 out = append(out, " "+th.FGColor(th.Muted, "changelog: ")+th.FGColor(color, info.URL))
M packages/agent/modes/update_banner_test.go +1 -1
@@ -3,7 +3,7 @@ package modes
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/tui"
6+ "github.com/bnema/zut/packages/tui"
77 )
88
99 func TestRenderUpdateBannerOmitsUnavailableUpdate(t *testing.T) {
M packages/agent/modes/welcome.go +6 -6
@@ -1,18 +1,18 @@
11 package modes
22
3-import "github.com/patriceckhart/zot/packages/tui"
3+import "github.com/bnema/zut/packages/tui"
44
55 // welcomeBanner returns the intro text shown at the top of an empty chat.
6-// It uses the `zot` label color (same as the assistant) for consistency.
6+// It uses the `zut` label color (same as the assistant) for consistency.
77 //
88 // When version is non-empty AND showVersion is true, the headline
9-// reads "i'm zot (vX.Y.Z). ..." so users see which build they're on
10-// the moment zot starts. After welcomeVersionDuration the caller
9+// reads "i'm zut (vX.Y.Z). ..." so users see which build they're on
10+// the moment zut starts. After welcomeVersionDuration the caller
1111 // flips showVersion off and the headline reverts to plain text.
1212 func welcomeBanner(th tui.Theme, version string, showVersion bool) []string {
13- text := "zot. yet another coding agent harness."
13+ text := "zut. yet another coding agent harness."
1414 if showVersion && version != "" {
15- text = "zot (" + version + "). yet another coding agent harness."
15+ text = "zut (" + version + "). yet another coding agent harness."
1616 }
1717 headline := th.AccentBar(th.Assistant) + th.FGColor(th.Assistant, tui.Bold(text))
1818 return []string{
M packages/agent/ponytail_test.go +8 -8
@@ -54,7 +54,7 @@ func TestResolvePonytailModeDefaultAndExplicitDisable(t *testing.T) {
5454 for _, tc := range cases {
5555 t.Run(tc.name, func(t *testing.T) {
5656 root := t.TempDir()
57- t.Setenv("ZOT_HOME", filepath.Join(root, "zot-home"))
57+ t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
5858 t.Setenv("HOME", filepath.Join(root, "home"))
5959 if tc.disabled {
6060 disabled := false
@@ -94,13 +94,13 @@ func btoi(value bool) int {
9494
9595 func TestResolvePonytailModeRetainsAddendumWithSystemFile(t *testing.T) {
9696 root := t.TempDir()
97- zotHome := filepath.Join(root, "zot-home")
98- t.Setenv("ZOT_HOME", zotHome)
97+ zutHome := filepath.Join(root, "zut-home")
98+ t.Setenv("ZUT_HOME", zutHome)
9999 t.Setenv("HOME", filepath.Join(root, "home"))
100- if err := os.MkdirAll(zotHome, 0o755); err != nil {
100+ if err := os.MkdirAll(zutHome, 0o755); err != nil {
101101 t.Fatal(err)
102102 }
103- if err := os.WriteFile(filepath.Join(zotHome, "SYSTEM.md"), []byte("custom system identity"), 0o600); err != nil {
103+ if err := os.WriteFile(filepath.Join(zutHome, "SYSTEM.md"), []byte("custom system identity"), 0o600); err != nil {
104104 t.Fatal(err)
105105 }
106106
@@ -125,7 +125,7 @@ func TestResolvePonytailModeRetainsAddendumWithSystemFile(t *testing.T) {
125125
126126 func TestResolvePonytailModePrecedesExplicitAppendPrompt(t *testing.T) {
127127 root := t.TempDir()
128- t.Setenv("ZOT_HOME", filepath.Join(root, "zot-home"))
128+ t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
129129 t.Setenv("HOME", filepath.Join(root, "home"))
130130
131131 const explicit = "explicit invocation guidance"
@@ -150,7 +150,7 @@ func TestResolvePonytailModePrecedesExplicitAppendPrompt(t *testing.T) {
150150
151151 func TestResolvePonytailModeAppliesAcrossHeadlessModesAndCustomIdentity(t *testing.T) {
152152 root := t.TempDir()
153- t.Setenv("ZOT_HOME", filepath.Join(root, "zot-home"))
153+ t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
154154 t.Setenv("HOME", filepath.Join(root, "home"))
155155
156156 modes := []Mode{"", ModePrint, ModeStream, ModeJSON, ModeRPC, ModeSubagentWorker}
@@ -178,7 +178,7 @@ func TestResolvePonytailModeAppliesAcrossHeadlessModesAndCustomIdentity(t *testi
178178 }
179179
180180 func TestConfigSettingsStorePersistsPonytailWithoutChangingKnownFields(t *testing.T) {
181- t.Setenv("ZOT_HOME", t.TempDir())
181+ t.Setenv("ZUT_HOME", t.TempDir())
182182 if err := SaveConfig(Config{Theme: "dark", Provider: "ollama", Model: "local"}); err != nil {
183183 t.Fatal(err)
184184 }
M packages/agent/print_stats_test.go +1 -1
@@ -7,7 +7,7 @@ import (
77 "testing"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 func TestWritePrintStats(t *testing.T) {
M packages/agent/rpc.go +12 -12
@@ -12,12 +12,12 @@ import (
1212 "sync"
1313 "time"
1414
15- "github.com/patriceckhart/zot/packages/agent/extensions"
16- "github.com/patriceckhart/zot/packages/agent/extproto"
17- "github.com/patriceckhart/zot/packages/agent/modes"
18- "github.com/patriceckhart/zot/packages/agent/tools"
19- "github.com/patriceckhart/zot/packages/core"
20- "github.com/patriceckhart/zot/packages/provider"
15+ "github.com/bnema/zut/packages/agent/extensions"
16+ "github.com/bnema/zut/packages/agent/extproto"
17+ "github.com/bnema/zut/packages/agent/modes"
18+ "github.com/bnema/zut/packages/agent/tools"
19+ "github.com/bnema/zut/packages/core"
20+ "github.com/bnema/zut/packages/provider"
2121 )
2222
2323 // runRPCMode implements the JSON-over-stdin/stdout RPC protocol.
@@ -42,7 +42,7 @@ import (
4242 // ext_widget with extension/id/position/title/lines, and ext_widget_clear
4343 // with extension/id when loaded extensions publish persistent UI state.
4444 //
45-// Auth: if $ZOTCORE_RPC_TOKEN is set, the first command must be
45+// Auth: if $ZUTCORE_RPC_TOKEN is set, the first command must be
4646 // {"type":"hello","token":"..."} or the connection is closed.
4747 func runRPCMode(ctx context.Context, args Args, version string) error {
4848 if args.NoYolo {
@@ -69,7 +69,7 @@ func runRPCMode(ctx context.Context, args Args, version string) error {
6969 // host-hooks integration. Notify/Display calls from extensions
7070 // emit RPC events instead of TUI lines so any consumer can react.
7171 extHooks := &rpcExtHooks{server: server}
72- extMgr := extensions.New(ZotHome(), r.CWD, version, r.Provider, r.Model, extHooks)
72+ extMgr := extensions.New(ZutHome(), r.CWD, version, r.Provider, r.Model, extHooks)
7373 for _, e := range extMgr.LoadExplicit(ctx, args.Exts) {
7474 fmt.Fprintln(os.Stderr, "extension load:", e)
7575 }
@@ -228,7 +228,7 @@ type rpcServer struct {
228228
229229 // inFlight tracks long-running command goroutines so run() can
230230 // wait for them before returning when stdin closes. Without this,
231- // piping a single 'prompt' command into 'zot rpc' would race the
231+ // piping a single 'prompt' command into 'zut rpc' would race the
232232 // process exit against the agent loop and the prompt would never
233233 // produce output.
234234 inFlight sync.WaitGroup
@@ -236,10 +236,10 @@ type rpcServer struct {
236236
237237 // run reads NDJSON commands from in and dispatches them. Returns when
238238 // in is closed AND every in-flight long-running command (prompt /
239-// compact) has finished, so a quick `echo cmd | zot rpc` invocation
239+// compact) has finished, so a quick `echo cmd | zut rpc` invocation
240240 // still produces full output before the process exits.
241241 func (s *rpcServer) run(in io.Reader) error {
242- requireToken := os.Getenv("ZOTCORE_RPC_TOKEN") != ""
242+ requireToken := os.Getenv("ZUTCORE_RPC_TOKEN") != ""
243243 s.writeMu.Lock()
244244 s.started = true
245245 s.authed = !requireToken
@@ -272,7 +272,7 @@ func (s *rpcServer) run(in io.Reader) error {
272272 Token string `json:"token"`
273273 }
274274 _ = json.Unmarshal([]byte(line), &hello)
275- if hello.Token != os.Getenv("ZOTCORE_RPC_TOKEN") {
275+ if hello.Token != os.Getenv("ZUTCORE_RPC_TOKEN") {
276276 s.writeError(head.ID, head.Type, "invalid token")
277277 return fmt.Errorf("rpc: bad auth token")
278278 }
M packages/agent/rpc_alert_test.go +2 -2
@@ -6,7 +6,7 @@ import (
66 "strings"
77 "testing"
88
9- "github.com/patriceckhart/zot/packages/agent/extproto"
9+ "github.com/bnema/zut/packages/agent/extproto"
1010 )
1111
1212 func TestRPCExtAlertIsJSONOnly(t *testing.T) {
@@ -27,7 +27,7 @@ func TestRPCExtAlertIsJSONOnly(t *testing.T) {
2727 }
2828
2929 func TestRPCExtAlertWaitsForAuthentication(t *testing.T) {
30- t.Setenv("ZOTCORE_RPC_TOKEN", "synthetic-token")
30+ t.Setenv("ZUTCORE_RPC_TOKEN", "synthetic-token")
3131
3232 var out bytes.Buffer
3333 server := &rpcServer{out: &out, provider: "test", model: "test-model", version: "test-version"}
M packages/agent/rpc_reasoning_test.go +1 -1
@@ -5,7 +5,7 @@ import (
55 "strings"
66 "testing"
77
8- "github.com/patriceckhart/zot/packages/core"
8+ "github.com/bnema/zut/packages/core"
99 )
1010
1111 func TestRPCSetReasoningMax(t *testing.T) {
M packages/agent/sdk/reasoning_test.go +1 -1
@@ -3,7 +3,7 @@ package sdk
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/core"
6+ "github.com/bnema/zut/packages/core"
77 )
88
99 func TestRuntimeSetReasoningMax(t *testing.T) {
M packages/agent/sdk/sdk.go +8 -8
@@ -1,4 +1,4 @@
1-// Package sdk is the public Go SDK for embedding the zot agent
1+// Package sdk is the public Go SDK for embedding the zut agent
22 // runtime in third-party programs. It is the only stable, importable
33 // surface the project exposes; everything under internal/ is subject
44 // to change without notice.
@@ -19,7 +19,7 @@
1919 // Runtime per project / cwd. The Cancel call interrupts the active
2020 // prompt; subsequent prompts work normally.
2121 //
22-// For a non-Go consumer, run `zot rpc` and speak the same JSON
22+// For a non-Go consumer, run `zut rpc` and speak the same JSON
2323 // schema over stdin/stdout. See docs/rpc.md.
2424 package sdk
2525
@@ -29,14 +29,14 @@ import (
2929 "fmt"
3030 "sync"
3131
32- "github.com/patriceckhart/zot/packages/agent"
33- agenttools "github.com/patriceckhart/zot/packages/agent/tools"
34- "github.com/patriceckhart/zot/packages/core"
35- "github.com/patriceckhart/zot/packages/provider"
32+ "github.com/bnema/zut/packages/agent"
33+ agenttools "github.com/bnema/zut/packages/agent/tools"
34+ "github.com/bnema/zut/packages/core"
35+ "github.com/bnema/zut/packages/provider"
3636 )
3737
3838 // Config configures a Runtime. All fields are optional; sensible
39-// defaults are read from $ZOT_HOME/config.json, env vars, and the
39+// defaults are read from $ZUT_HOME/config.json, env vars, and the
4040 // resolver chain (the same one the cli uses).
4141 type Config struct {
4242 // Provider is "anthropic" or "openai". Empty = use the user's
@@ -87,7 +87,7 @@ type Config struct {
8787 Lock bool
8888 }
8989
90-// Runtime is one zot agent session. Safe for use from one goroutine
90+// Runtime is one zut agent session. Safe for use from one goroutine
9191 // at a time per Runtime; create separate Runtimes for parallel work.
9292 type Runtime struct {
9393 mu sync.Mutex
M packages/agent/sdk/types.go +3 -3
@@ -3,14 +3,14 @@ package sdk
33 import (
44 "encoding/json"
55
6- "github.com/patriceckhart/zot/packages/core"
7- "github.com/patriceckhart/zot/packages/provider"
6+ "github.com/bnema/zut/packages/core"
7+ "github.com/bnema/zut/packages/provider"
88 )
99
1010 // Event is the unit emitted by Runtime.Prompt's channel. Type
1111 // identifies the event kind; remaining fields are populated based on
1212 // kind. The wire schema is the same as the JSON-RPC stream notifications
13-// emitted by `zot rpc`, so consumers can share parsing code.
13+// emitted by `zut rpc`, so consumers can share parsing code.
1414 type Event struct {
1515 Type string `json:"type"`
1616
M packages/agent/settings_store.go +2 -2
@@ -3,8 +3,8 @@ package agent
33 import (
44 "fmt"
55
6- "github.com/patriceckhart/zot/packages/provider"
7- "github.com/patriceckhart/zot/packages/tui"
6+ "github.com/bnema/zut/packages/provider"
7+ "github.com/bnema/zut/packages/tui"
88 )
99
1010 type configSettingsStore struct{}
M packages/agent/settings_store_test.go +7 -7
@@ -3,7 +3,7 @@ package agent
33 import "testing"
44
55 func TestConfigSettingsStorePersistsShowInstructionsAtStartup(t *testing.T) {
6- t.Setenv("ZOT_HOME", t.TempDir())
6+ t.Setenv("ZUT_HOME", t.TempDir())
77 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
88 t.Fatal(err)
99 }
@@ -24,7 +24,7 @@ func TestConfigSettingsStorePersistsShowInstructionsAtStartup(t *testing.T) {
2424 }
2525
2626 func TestConfigSettingsStorePersistsTerminalAlerts(t *testing.T) {
27- t.Setenv("ZOT_HOME", t.TempDir())
27+ t.Setenv("ZUT_HOME", t.TempDir())
2828 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
2929 t.Fatal(err)
3030 }
@@ -45,7 +45,7 @@ func TestConfigSettingsStorePersistsTerminalAlerts(t *testing.T) {
4545 }
4646
4747 func TestConfigSettingsStorePersistsTerminalTitle(t *testing.T) {
48- t.Setenv("ZOT_HOME", t.TempDir())
48+ t.Setenv("ZUT_HOME", t.TempDir())
4949 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
5050 t.Fatal(err)
5151 }
@@ -66,7 +66,7 @@ func TestConfigSettingsStorePersistsTerminalTitle(t *testing.T) {
6666 }
6767
6868 func TestConfigSettingsStorePersistsJailByDefault(t *testing.T) {
69- t.Setenv("ZOT_HOME", t.TempDir())
69+ t.Setenv("ZUT_HOME", t.TempDir())
7070 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
7171 t.Fatal(err)
7272 }
@@ -87,7 +87,7 @@ func TestConfigSettingsStorePersistsJailByDefault(t *testing.T) {
8787 }
8888
8989 func TestConfigSettingsStorePersistsAutoCompactThreshold(t *testing.T) {
90- t.Setenv("ZOT_HOME", t.TempDir())
90+ t.Setenv("ZUT_HOME", t.TempDir())
9191 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
9292 t.Fatal(err)
9393 }
@@ -108,7 +108,7 @@ func TestConfigSettingsStorePersistsAutoCompactThreshold(t *testing.T) {
108108 }
109109
110110 func TestConfigSettingsStorePersistsLSPDefaults(t *testing.T) {
111- t.Setenv("ZOT_HOME", t.TempDir())
111+ t.Setenv("ZUT_HOME", t.TempDir())
112112 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
113113 t.Fatal(err)
114114 }
@@ -163,7 +163,7 @@ func TestConfigLSPEnabledForDefaultsToTrue(t *testing.T) {
163163 }
164164
165165 func TestConfigSettingsStorePersistsInheritedTheme(t *testing.T) {
166- t.Setenv("ZOT_HOME", t.TempDir())
166+ t.Setenv("ZUT_HOME", t.TempDir())
167167 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
168168 t.Fatal(err)
169169 }
M packages/agent/skills/builtin.go +1 -1
@@ -7,7 +7,7 @@ import (
77 "strings"
88 )
99
10-// builtinFS holds the SKILL.md files zot ships with the binary.
10+// builtinFS holds the SKILL.md files zut ships with the binary.
1111 // They appear in the catalogue as ordinary skills — same on-demand
1212 // load via the `skill` tool, same /skills picker — but never need
1313 // to be installed by the user. A user-installed skill with the same
R packages/agent/skills/builtin/write-zot-extension/SKILL.md → packages/agent/skills/builtin/write-zut-extension/SKILL.md +48 -44 renamed, 82% similar
@@ -1,23 +1,23 @@
11 ---
2-name: write-zot-extension
3-description: Help the user create a new zot extension (slash command, LLM tool, or guard) in any language.
2+name: write-zut-extension
3+description: Help the user create a new zut extension (slash command, LLM tool, or guard) in any language.
44 ---
55
6-# Writing a zot extension
6+# Writing a zut extension
77
8-Use this skill when the user asks for help building a zot extension —
8+Use this skill when the user asks for help building a zut extension —
99 a new slash command, a new tool the LLM can call, an audit hook, or
1010 a permission gate. Skim this whole skill first, then collaborate
1111 with the user on the specific extension they want.
1212
1313 ## What an extension is
1414
15-A zot extension is **an external executable** that zot launches as a
15+A zut extension is **an external executable** that zut launches as a
1616 subprocess and talks to over its stdin/stdout in newline-delimited
1717 JSON. It can be written in any language that can read/write JSON
1818 lines from stdio: Go, TypeScript (via tsx), Python, Rust, shell with
1919 jq, anything. Crash isolation is automatic; one bad extension never
20-takes down zot.
20+takes down zut.
2121
2222 Three things an extension can do (any combination):
2323
@@ -27,7 +27,7 @@ Three things an extension can do (any combination):
2727 (one-shot styled note in the chat), or a "noop".
2828
2929 2. **Tools** — register tools the LLM itself calls. Schema is
30- JSON Schema; zot routes the model's `tool_call` to the
30+ JSON Schema; zut routes the model's `tool_call` to the
3131 extension's `tool_result`. Same lifecycle as built-in tools
3232 (read/write/edit/bash/lsp/skill).
3333
@@ -42,16 +42,16 @@ Three things an extension can do (any combination):
4242 Each extension lives in its own directory:
4343
4444 ```
45-~/Library/Application Support/zot/extensions/<name>/
45+~/Library/Application Support/zut/extensions/<name>/
4646 ├── extension.json # manifest (required)
4747 └── <executable> # whatever exec points at
4848 ```
4949
50-Or project-local: `<project>/.zot/extensions/<name>/`. Project-local
50+Or project-local: `<project>/.zut/extensions/<name>/`. Project-local
5151 wins on name conflict.
5252
5353 For ad-hoc use during development, skip the install step entirely
54-and run `zot --ext PATH` (repeatable: `-e PATH -e PATH`).
54+and run `zut --ext PATH` (repeatable: `-e PATH -e PATH`).
5555
5656 ### Manifest
5757
@@ -68,7 +68,7 @@ and run `zot --ext PATH` (repeatable: `-e PATH -e PATH`).
6868 ```
6969
7070 Field rules:
71-- `name` (required, unique) — id zot uses internally; matches the
71+- `name` (required, unique) — id zut uses internally; matches the
7272 hello frame. Slash commands & tools live in the same name space
7373 as built-ins; conflicts are silently shadowed by built-ins.
7474 - `exec` (required) — the executable path. Resolution:
@@ -92,17 +92,18 @@ their responses.
9292 The very first frame the extension sends is `hello`:
9393
9494 ```json
95-{"type":"hello","name":"weather","version":"1.0.0",
95+{"type":"hello","protocol_version":2,"name":"weather","version":"1.0.0",
9696 "capabilities":["commands","tools"]}
9797 ```
9898
9999 Capabilities are advisory; current values are `commands`, `tools`,
100-`events`, `alerts`, and `panels`. Send all that apply.
100+`events`, `alerts`, and `panels`. Send all that apply. `protocol_version`
101+is the major wire version; it is currently `2` and must match the host.
101102
102-zot replies with `hello_ack`:
103+zut replies with `hello_ack`:
103104
104105 ```json
105-{"type":"hello_ack","protocol_version":1,"zot_version":"0.0.x",
106+{"type":"hello_ack","protocol_version":2,"zut_version":"0.0.x",
106107 "provider":"anthropic","model":"claude-opus-4-7","cwd":"/path/to/project"}
107108 ```
108109
@@ -110,7 +111,7 @@ zot replies with `hello_ack`:
110111
111112 The canonical startup order is `hello`, wait for `hello_ack`, send
112113 registration frames in any order, then send a single `ready` sentinel
113-so zot can finalize the agent's tool registry:
114+so zut can finalize the agent's tool registry:
114115
115116 ```json
116117 {"type":"register_command","name":"weather","description":"current weather"}
@@ -120,7 +121,7 @@ so zot can finalize the agent's tool registry:
120121 {"type":"ready"}
121122 ```
122123
123-If you don't send `ready`, zot's idle watchdog auto-treats you as
124+If you don't send `ready`, zut's idle watchdog auto-treats you as
124125 ready after 250ms of no frames, but always send it explicitly when
125126 you can. Newer extensions on faster hosts shave that 250ms off.
126127
@@ -130,7 +131,7 @@ process itself runs from the extension directory, so do not use
130131
131132 ### Runtime frames
132133
133-**zot → extension:**
134+**zut → extension:**
134135
135136 ```json
136137 {"type":"command_invoked","id":"abc","name":"weather","args":"berlin"}
@@ -141,7 +142,7 @@ process itself runs from the extension directory, so do not use
141142 {"type":"shutdown"}
142143 ```
143144
144-**extension → zot (replies + spontaneous notifications):**
145+**extension → zut (replies + spontaneous notifications):**
145146
146147 ```json
147148 {"type":"command_response","id":"abc","action":"prompt",
@@ -171,7 +172,7 @@ stdout.
171172 - `"insert"` — drop `insert` into the editor at the cursor
172173 - `"display"` — append `display` to chat as a one-shot note (no
173174 model call, not in transcript)
174-- `"noop"` — handled internally; zot doesn't change the UI
175+- `"noop"` — handled internally; zut doesn't change the UI
175176
176177 `tool_result.content[]` blocks: `{"type":"text","text":"..."}` or
177178 `{"type":"image","mime_type":"image/png","data":"<base64>"}`.
@@ -186,7 +187,7 @@ never stalls the agent.
186187 stdout that isn't a JSON frame breaks the wire. The first stdout
187188 frame must be `hello`; do not send `notify`, `alert`, logs, or
188189 registration frames before that handshake starts. Use stderr for
189- logs / debug output (zot captures stderr to `$ZOT_HOME/logs/ext-<name>.log`).
190+ logs / debug output (zut captures stderr to `$ZUT_HOME/logs/ext-<name>.log`).
190191 - **One JSON object per line.** No multi-line JSON. Always end
191192 every frame with `\n`.
192193 - **Flush after writing.** Most stdout writes are line-buffered when
@@ -204,7 +205,7 @@ package main
204205
205206 import (
206207 "encoding/json"
207- "github.com/patriceckhart/zot/packages/agent/ext"
208+ "github.com/bnema/zut/packages/agent/ext"
208209 )
209210
210211 func main() {
@@ -243,7 +244,7 @@ func main() {
243244 Build: `go build -o weather .`
244245
245246 `OnHello` is optional. Use it when configuration or registrations need
246-host metadata such as `HostInfo.CWD`, `Provider`, `Model`, `ZotVersion`,
247+host metadata such as `HostInfo.CWD`, `Provider`, `Model`, `ZutVersion`,
247248 `ExtensionDir`, or `DataDir`. The SDK sends `hello`, waits for
248249 `hello_ack`, runs `OnHello`, announces registrations, then sends `ready`.
249250
@@ -253,7 +254,7 @@ host metadata such as `HostInfo.CWD`, `Provider`, `Model`, `ZotVersion`,
253254 ```
254255
255256 After the hello handshake completes inside `Run`, call `e.Alert` from a
256-command, tool, or panel handler when the user needs to be drawn back to zot:
257+command, tool, or panel handler when the user needs to be drawn back to zut:
257258
258259 ```go
259260 e.Alert(ext.AlertRequest{Kind: ext.AlertKindBell, Reason: "question_ready"})
@@ -275,13 +276,14 @@ import { stderr, stdin, stdout } from "node:process";
275276 function send(o: object) { stdout.write(JSON.stringify(o) + "\n"); }
276277 function log(s: string) { stderr.write(`[scratchpad] ${s}\n`); }
277278
278-send({ type: "hello", name: "scratchpad", version: "1.0.0",
279+send({ type: "hello", protocol_version: 2, name: "scratchpad", version: "1.0.0",
279280 capabilities: ["commands", "tools"] });
280281
281282 const rl = createInterface({ input: stdin, crlfDelay: Infinity });
282283 rl.on("line", (line) => {
283284 const f = JSON.parse(line);
284285 if (f.type === "hello_ack") {
286+ if (f.protocol_version !== 2) throw new Error("unsupported extension protocol version");
285287 // f.cwd is the user's project directory.
286288 send({ type: "register_command", name: "note", description: "append a note" });
287289 send({ type: "register_tool", name: "read_notes",
@@ -317,11 +319,13 @@ import json, sys
317319
318320 def emit(o): sys.stdout.write(json.dumps(o) + "\n"); sys.stdout.flush()
319321
320-emit({"type": "hello", "name": "hello-py", "version": "1.0.0", "capabilities": ["commands"]})
322+emit({"type": "hello", "protocol_version": 2, "name": "hello-py", "version": "1.0.0", "capabilities": ["commands"]})
321323
322324 for line in sys.stdin:
323325 msg = json.loads(line)
324326 if msg["type"] == "hello_ack":
327+ if msg.get("protocol_version") != 2:
328+ raise SystemExit("unsupported extension protocol version")
325329 # msg["cwd"] is the user's project directory.
326330 emit({"type": "register_command", "name": "hellopy", "description": "say hi (python)"})
327331 emit({"type": "ready"})
@@ -338,24 +342,24 @@ for line in sys.stdin:
338342 ## Install / dev workflow
339343
340344 ```bash
341-zot ext install ./weather # copy into $ZOT_HOME/extensions/
342-zot ext install --build=go ./weather # explicitly build local Go source
343-zot --ext ./weather # run from disk for one zot session (no install)
344-zot --ext . # cwd is the extension dir
345-zot ext list # show installed extensions
346-zot ext logs weather # cat the extension's stderr
347-zot ext logs weather -f # tail it
348-zot ext disable weather # keep installed but skip on launch
349-zot ext enable weather
350-zot ext remove weather
345+zut ext install ./weather # copy into $ZUT_HOME/extensions/
346+zut ext install --build=go ./weather # explicitly build local Go source
347+zut --ext ./weather # run from disk for one zut session (no install)
348+zut --ext . # cwd is the extension dir
349+zut ext list # show installed extensions
350+zut ext logs weather # cat the extension's stderr
351+zut ext logs weather -f # tail it
352+zut ext disable weather # keep installed but skip on launch
353+zut ext enable weather
354+zut ext remove weather
351355 ```
352356
353357 For TS / Python extensions, no build step is needed — edit the source
354-in place and relaunch zot.
358+in place and relaunch zut.
355359
356-For Go, either run `zot ext install --build=go <path>` to explicitly build
360+For Go, either run `zut ext install --build=go <path>` to explicitly build
357361 the local source into the staged install, or run `go build -o <name> .`
358-first and then use `zot ext install <path>`. The default install never runs a
362+first and then use `zut ext install <path>`. The default install never runs a
359363 build step; a missing local executable is rejected with an explicit builder
360364 hint instead of leaving a broken installed extension. The `--build=go` option
361365 is intentionally limited to local source paths and does not apply to git URLs.
@@ -367,7 +371,7 @@ to see exactly what's happening on the wire:
367371
368372 ```bash
369373 {
370- printf '%s\n' '{"type":"hello_ack","protocol_version":1,"zot_version":"x","provider":"a","model":"o","cwd":"/tmp"}'
374+ printf '%s\n' '{"type":"hello_ack","protocol_version":2,"zut_version":"x","provider":"a","model":"o","cwd":"/tmp"}'
371375 sleep 0.2
372376 printf '%s\n' '{"type":"command_invoked","id":"1","name":"weather","args":"Berlin"}'
373377 sleep 0.5
@@ -376,7 +380,7 @@ to see exactly what's happening on the wire:
376380 ```
377381
378382 Compare what comes out of stdout to the expected wire format. If a
379-frame doesn't match what zot expects, it's discarded silently and
383+frame doesn't match what zut expects, it's discarded silently and
380384 logged to `ext-<name>.log`.
381385
382386 ## Process to follow with the user
@@ -391,10 +395,10 @@ logged to `ext-<name>.log`.
391395 if they prefer JS-flavored ergonomics; **Python** for one-off
392396 scripts.
393397 4. Write the extension dir (manifest + source).
394-5. For Go, use `zot ext install --build=go <path>` or build it manually.
398+5. For Go, use `zut ext install --build=go <path>` or build it manually.
395399 For TS / Python, mark the script executable.
396-6. Suggest `zot --ext <path>` for testing without committing to an install.
397-7. When happy, `zot ext install <path>` (or `--build=go` for local Go source).
400+6. Suggest `zut --ext <path>` for testing without committing to an install.
401+7. When happy, `zut ext install <path>` (or `--build=go` for local Go source).
398402
399403 Don't try to write a full SDK or framework on top of the protocol
400404 unless the user asked for one — the wire format is small enough
R packages/agent/skills/builtin/write-zot-themes/SKILL.md → packages/agent/skills/builtin/write-zut-themes/SKILL.md +29 -29 renamed, 83% similar
@@ -1,17 +1,17 @@
11 ---
2-name: write-zot-themes
3-description: Help the user create, install, or package zot themes, including theme-only extensions.
2+name: write-zut-themes
3+description: Help the user create, install, or package zut themes, including theme-only extensions.
44 ---
55
6-# Writing zot themes
6+# Writing zut themes
77
88 Use this skill when the user asks for help creating, editing,
9-installing, debugging, or packaging a zot color theme. Read this skill
9+installing, debugging, or packaging a zut color theme. Read this skill
1010 before generating a theme file or advising on theme extension layout.
1111
12-## What a zot theme is
12+## What a zut theme is
1313
14-A zot theme is a JSON file that overrides any subset of zot's built-in
14+A zut theme is a JSON file that overrides any subset of zut's built-in
1515 light/dark theme values. Theme files are intentionally permissive:
1616 nothing is required. A file may contain only colors, only spinner
1717 settings, only syntax colors, or only metadata. Missing values always
@@ -27,19 +27,19 @@ unavailable.
2727 User themes are discovered from:
2828
2929 ```text
30-$ZOT_HOME/themes/*.json
30+$ZUT_HOME/themes/*.json
3131 ```
3232
3333 Extension themes are discovered in-place from loaded extension dirs:
3434
3535 ```text
36-$ZOT_HOME/extensions/<extension>/theme.json
37-$ZOT_HOME/extensions/<extension>/themes/theme.json
38-<project>/.zot/extensions/<extension>/theme.json
39-<project>/.zot/extensions/<extension>/themes/theme.json
36+$ZUT_HOME/extensions/<extension>/theme.json
37+$ZUT_HOME/extensions/<extension>/themes/theme.json
38+<project>/.zut/extensions/<extension>/theme.json
39+<project>/.zut/extensions/<extension>/themes/theme.json
4040 ```
4141
42-zot does **not** copy extension themes into `$ZOT_HOME/themes`; extension
42+zut does **not** copy extension themes into `$ZUT_HOME/themes`; extension
4343 owned themes stay in the extension directory. The settings picker stores
4444 an absolute path for extension-owned themes and loads that file directly.
4545
@@ -47,12 +47,12 @@ an absolute path for extension-owned themes and loads that file directly.
4747
4848 ### Empty / metadata-only theme
4949
50-Valid; inherits all colors/spinner/syntax from zot defaults.
50+Valid; inherits all colors/spinner/syntax from zut defaults.
5151
5252 ```json
5353 {
5454 "name": "my-theme",
55- "description": "Metadata only; all visuals inherit zot defaults."
55+ "description": "Metadata only; all visuals inherit zut defaults."
5656 }
5757 ```
5858
@@ -95,7 +95,7 @@ Top-level spinner overrides are valid and apply to both modes.
9595
9696 ### Dark-only theme also works in light terminals
9797
98-If `colors.light` is missing, zot applies `colors.dark` overrides on
98+If `colors.light` is missing, zut applies `colors.dark` overrides on
9999 top of the built-in light default when running in light mode. The
100100 inverse is also true: if dark is missing but light exists, light
101101 settings are used on dark defaults.
@@ -103,7 +103,7 @@ settings are used on dark defaults.
103103 ```json
104104 {
105105 "name": "custom-spinner",
106- "description": "An alternative spinner for zot that only displays a single spinner text.",
106+ "description": "An alternative spinner for zut that only displays a single spinner text.",
107107 "colors": {
108108 "dark": {
109109 "spinner_frames": ["◢", "◣", "◤", "◥"],
@@ -123,7 +123,7 @@ All fields below are optional.
123123 "name": "my-theme",
124124 "description": "Shown in /settings → color theme.",
125125 "color_descriptions": {
126- "accent": "Optional documentation for humans. zot ignores this object."
126+ "accent": "Optional documentation for humans. zut ignores this object."
127127 },
128128 "colors": {
129129 "dark": {
@@ -198,7 +198,7 @@ under `colors`, when they should apply to both modes:
198198 - `user` — user role label color; mostly compatibility.
199199 - `user_bubble_bg` — background behind user message rows.
200200 - `user_bubble_fg` — foreground inside user message rows.
201-- `assistant` — assistant/zot accent and spinner text.
201+- `assistant` — assistant/zut accent and spinner text.
202202 - `tool` — tool names, success marks, diff additions.
203203 - `tool_out` — plain tool-output text.
204204 - `error` — errors, refused calls, diff deletions.
@@ -230,7 +230,7 @@ Spinner settings can appear at top level, under `colors`, or under
230230
231231 - `spinner_frames` — list of frame strings. Use single-cell glyphs
232232 when possible so status-bar alignment stays clean.
233-- `spinner_messages` — list of messages; zot chooses one per turn.
233+- `spinner_messages` — list of messages; zut chooses one per turn.
234234 - `spinner_interval_ms` — frame interval in milliseconds; must be
235235 positive. Missing/invalid falls back to 80ms.
236236
@@ -291,26 +291,26 @@ Example:
291291 ## Installing a user theme
292292
293293 ```bash
294-mkdir -p "$ZOT_HOME/themes"
295-cp my-theme.json "$ZOT_HOME/themes/my-theme.json"
294+mkdir -p "$ZUT_HOME/themes"
295+cp my-theme.json "$ZUT_HOME/themes/my-theme.json"
296296 ```
297297
298-Then open `/settings` and choose **color theme**. zot switches theme
299-immediately and persists the selection in `$ZOT_HOME/config.json`.
298+Then open `/settings` and choose **color theme**. zut switches theme
299+immediately and persists the selection in `$ZUT_HOME/config.json`.
300300
301-If a selected theme file is deleted, zot resets the setting to the
301+If a selected theme file is deleted, zut resets the setting to the
302302 built-in auto/default theme.
303303
304304 ## Theme-only extensions
305305
306-A zot extension can exist only to ship a theme. No slash command,
306+A zut extension can exist only to ship a theme. No slash command,
307307 subprocess, or executable is required when the extension contains a
308308 valid theme file.
309309
310310 Layout:
311311
312312 ```text
313-$ZOT_HOME/extensions/my-theme-extension/
313+$ZUT_HOME/extensions/my-theme-extension/
314314 ├── extension.json
315315 └── theme.json
316316 ```
@@ -318,7 +318,7 @@ $ZOT_HOME/extensions/my-theme-extension/
318318 or:
319319
320320 ```text
321-$ZOT_HOME/extensions/my-theme-extension/
321+$ZUT_HOME/extensions/my-theme-extension/
322322 ├── extension.json
323323 └── themes/
324324 └── theme.json
@@ -330,13 +330,13 @@ $ZOT_HOME/extensions/my-theme-extension/
330330 {
331331 "name": "my-theme-extension",
332332 "version": "1.0.0",
333- "description": "Ships a zot color theme",
333+ "description": "Ships a zut color theme",
334334 "enabled": true
335335 }
336336 ```
337337
338338 No `exec` is needed when `theme.json` or `themes/theme.json` exists.
339-If `exec` is present, zot treats it as a normal extension too.
339+If `exec` is present, zut treats it as a normal extension too.
340340
341341 In `/settings → color theme`, extension themes show source info in
342342 the description, e.g. `from extension my-theme-extension — ...`.
M packages/agent/skills/skills.go +21 -21
@@ -1,8 +1,8 @@
1-// Package skills implements zot's reusable-instruction system.
1+// Package skills implements zut's reusable-instruction system.
22 //
33 // A skill is a per-folder SKILL.md file with a YAML frontmatter
44 // header. Skills live in well-known directories under the project or
5-// the user home; zot discovers them at startup, lists their names +
5+// the user home; zut discovers them at startup, lists their names +
66 // one-line descriptions in the system prompt, and exposes a built-in
77 // "skill" tool the model uses to pull the full body on demand.
88 //
@@ -12,15 +12,15 @@
1212 //
1313 // Discovery layout (priority order — first match wins per name):
1414 //
15-// ./.zot/skills/<name>/SKILL.md — project (native)
16-// $ZOT_HOME/skills/<name>/SKILL.md — global (native)
15+// ./.zut/skills/<name>/SKILL.md — project (native)
16+// $ZUT_HOME/skills/<name>/SKILL.md — global (native)
1717 // ./.claude/skills/<name>/SKILL.md — project (claude-compat)
1818 // ~/.claude/skills/<name>/SKILL.md — global (claude-compat)
1919 // ./.agents/skills/<name>/SKILL.md — project (agent-compat)
2020 // ~/.agents/skills/<name>/SKILL.md — global (agent-compat)
2121 //
2222 // The compat paths are deliberate: a SKILL.md written for any of
23-// the related ecosystems works in zot unchanged. User skill roots are
23+// the related ecosystems works in zut unchanged. User skill roots are
2424 // walked recursively, and nested files can also be addressed by their
2525 // slash-separated path relative to the root (for example,
2626 // systems-backend/subskills/golang-patterns).
@@ -63,7 +63,7 @@ type Skill struct {
6363 // Shown in the /skills picker.
6464 Source string
6565
66- // Builtin marks skills that ship inside the zot binary. They are
66+ // Builtin marks skills that ship inside the zut binary. They are
6767 // fully active for the model (system-prompt manifest + skill
6868 // tool) but hidden from user-facing surfaces like the /skills
6969 // picker so users only see skills they actually installed or
@@ -121,18 +121,18 @@ func VisibleSkills(in []*Skill) []*Skill {
121121 //
122122 // Errors per skill are returned alongside the partial result so a
123123 // single broken file doesn't suppress the rest.
124-func Discover(zotHome, cwd, userHome string, includeUser bool) ([]*Skill, []error) {
125- return DiscoverWithBundled(zotHome, cwd, userHome, includeUser, nil)
124+func Discover(zutHome, cwd, userHome string, includeUser bool) ([]*Skill, []error) {
125+ return DiscoverWithBundled(zutHome, cwd, userHome, includeUser, nil)
126126 }
127127
128128 // DiscoverWithBundled merges normal user skills with extension-bundled skills
129129 // and built-ins. Precedence is user/project locations, then bundled skills in
130130 // declaration order, then built-ins. The first matching name wins.
131-func DiscoverWithBundled(zotHome, cwd, userHome string, includeUser bool, bundled []BundledSkillDir) ([]*Skill, []error) {
131+func DiscoverWithBundled(zutHome, cwd, userHome string, includeUser bool, bundled []BundledSkillDir) ([]*Skill, []error) {
132132 var errs []error
133133 seen := map[string]*Skill{}
134134 if includeUser {
135- errs = append(errs, scanUserSkills(zotHome, cwd, userHome, seen)...)
135+ errs = append(errs, scanUserSkills(zutHome, cwd, userHome, seen)...)
136136 }
137137 for _, dir := range bundled {
138138 errs = append(errs, scanBundledSkills(dir, seen)...)
@@ -250,9 +250,9 @@ func scanBundledSkills(dir BundledSkillDir, seen map[string]*Skill) []error {
250250 // populates `seen` with first-match-wins per name or alias. Split out
251251 // so Discover's includeUser=false path doesn't have to skip over a
252252 // giant block.
253-func scanUserSkills(zotHome, cwd, userHome string, seen map[string]*Skill) []error {
253+func scanUserSkills(zutHome, cwd, userHome string, seen map[string]*Skill) []error {
254254 var errs []error
255- for _, loc := range searchDirs(zotHome, cwd, userHome) {
255+ for _, loc := range searchDirs(zutHome, cwd, userHome) {
256256 _ = filepath.WalkDir(loc.dir, func(path string, entry fs.DirEntry, walkErr error) error {
257257 if walkErr != nil {
258258 if !os.IsNotExist(walkErr) {
@@ -362,7 +362,7 @@ func uniqueSkills(seen map[string]*Skill) []*Skill {
362362 // The format is deliberately compact: name, one-line description,
363363 // and a source pointer telling the model where the full body
364364 // lives. Built-in skills show "builtin" since their markdown is
365-// embedded in the zot binary and not on the filesystem; user
365+// embedded in the zut binary and not on the filesystem; user
366366 // skills show their SKILL.md path (shortened with ~ for HOME).
367367 //
368368 // Loading still goes through the `skill` tool with just the name.
@@ -391,7 +391,7 @@ func SystemPromptAddendum(skills []*Skill) string {
391391
392392 // skillSourcePointer returns a short tag describing where a skill
393393 // originates. Built-ins are tagged "builtin" because their markdown
394-// is embedded in the zot binary and not reachable through the
394+// is embedded in the zut binary and not reachable through the
395395 // filesystem. User skills are tagged with their SKILL.md path,
396396 // collapsed to use ~ for the user home when possible.
397397 func skillSourcePointer(s *Skill, home string) string {
@@ -438,7 +438,7 @@ type location struct {
438438 label string
439439 }
440440
441-func searchDirs(zotHome, cwd, userHome string) []location {
441+func searchDirs(zutHome, cwd, userHome string) []location {
442442 var out []location
443443 add := func(dir, label string) {
444444 if dir == "" {
@@ -446,16 +446,16 @@ func searchDirs(zotHome, cwd, userHome string) []location {
446446 }
447447 out = append(out, location{dir: dir, label: label})
448448 }
449- if extra := os.Getenv("ZOT_AGENT_SKILLS"); extra != "" {
449+ if extra := os.Getenv("ZUT_AGENT_SKILLS"); extra != "" {
450450 for _, dir := range filepath.SplitList(extra) {
451451 add(dir, "agent")
452452 }
453453 }
454454 if cwd != "" {
455- add(filepath.Join(cwd, ".zot", "skills"), "project")
455+ add(filepath.Join(cwd, ".zut", "skills"), "project")
456456 }
457- if zotHome != "" {
458- add(filepath.Join(zotHome, "skills"), "global")
457+ if zutHome != "" {
458+ add(filepath.Join(zutHome, "skills"), "global")
459459 }
460460 if cwd != "" {
461461 add(filepath.Join(cwd, ".claude", "skills"), "project (claude)")
@@ -510,14 +510,14 @@ func splitFrontmatter(raw string) (string, string) {
510510 return front, body
511511 }
512512
513-// parseFrontmatter handles the small subset of YAML zot recognizes:
513+// parseFrontmatter handles the small subset of YAML zut recognizes:
514514 // - simple `key: value` lines
515515 // - `key: [a, b, c]` flow-style lists
516516 // - `key:` followed by indented `- item` block lists
517517 // - nested `key:` followed by indented `subkey: [...]` for permissions
518518 //
519519 // Anything more elaborate is ignored. We deliberately avoid a yaml
520-// dependency to keep zot's binary lean.
520+// dependency to keep zut's binary lean.
521521 func parseFrontmatter(front string, s *Skill) {
522522 lines := strings.Split(front, "\n")
523523 i := 0
M packages/agent/skills/skills_test.go +10 -10
@@ -50,9 +50,9 @@ permissions:
5050 }
5151
5252 func TestDiscoverProjectAndGlobalPriorityAndDedup(t *testing.T) {
53- t.Setenv("ZOT_AGENT_SKILLS", "")
53+ t.Setenv("ZUT_AGENT_SKILLS", "")
5454 tmp := t.TempDir()
55- zotHome := filepath.Join(tmp, "home")
55+ zutHome := filepath.Join(tmp, "home")
5656 cwd := filepath.Join(tmp, "proj")
5757
5858 mk := func(dir, name, desc string) {
@@ -63,17 +63,17 @@ func TestDiscoverProjectAndGlobalPriorityAndDedup(t *testing.T) {
6363 }
6464
6565 // Same skill name in BOTH project and global; project should win.
66- mk(filepath.Join(cwd, ".zot", "skills"), "shared", "project version")
67- mk(filepath.Join(zotHome, "skills"), "shared", "global version")
66+ mk(filepath.Join(cwd, ".zut", "skills"), "shared", "project version")
67+ mk(filepath.Join(zutHome, "skills"), "shared", "global version")
6868 // Unique skill in global only.
69- mk(filepath.Join(zotHome, "skills"), "global-only", "from global")
69+ mk(filepath.Join(zutHome, "skills"), "global-only", "from global")
7070
71- skills, errs := Discover(zotHome, cwd, "", true /* includeUser */)
71+ skills, errs := Discover(zutHome, cwd, "", true /* includeUser */)
7272 if len(errs) > 0 {
7373 t.Fatalf("errs: %v", errs)
7474 }
7575 // Expect the two user skills + every built-in shipped with the
76- // binary (currently the write-zot-extension authoring guide).
76+ // binary (currently the write-zut-extension authoring guide).
7777 builtins := loadBuiltins()
7878 want := 2 + len(builtins)
7979 if len(skills) != want {
@@ -95,10 +95,10 @@ func TestDiscoverProjectAndGlobalPriorityAndDedup(t *testing.T) {
9595 }
9696
9797 func TestDiscoverWithBundledSkillsPrecedence(t *testing.T) {
98- t.Setenv("ZOT_AGENT_SKILLS", "")
98+ t.Setenv("ZUT_AGENT_SKILLS", "")
9999 tmp := t.TempDir()
100100 cwd := filepath.Join(tmp, "project")
101- userDir := filepath.Join(cwd, ".zot", "skills")
101+ userDir := filepath.Join(cwd, ".zut", "skills")
102102 bundleA := filepath.Join(tmp, "ext-a", "skills")
103103 bundleB := filepath.Join(tmp, "ext-b", "skills")
104104 write := func(root, name, description string) {
@@ -165,7 +165,7 @@ func TestVisibleSkillsHidesBuiltins(t *testing.T) {
165165 }
166166
167167 func TestDiscoverRecursesNestedAgentSkillsAndSupportsPathAlias(t *testing.T) {
168- t.Setenv("ZOT_AGENT_SKILLS", "")
168+ t.Setenv("ZUT_AGENT_SKILLS", "")
169169 tmp := t.TempDir()
170170 home := filepath.Join(tmp, "home")
171171 cwd := filepath.Join(tmp, "project")
M packages/agent/skills/tool.go +3 -3
@@ -6,8 +6,8 @@ import (
66 "fmt"
77 "sync"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 // Tool implements core.Tool, exposing a `skill` tool the LLM can call
@@ -84,7 +84,7 @@ func (t *Tool) Execute(ctx context.Context, args json.RawMessage, progress func(
8484 if s == nil {
8585 return core.ToolResult{
8686 IsError: true,
87- Content: []provider.Content{provider.TextBlock{Text: fmt.Sprintf("skill: no skill named %q (run /skills in zot to see what's available)", in.Name)}},
87+ Content: []provider.Content{provider.TextBlock{Text: fmt.Sprintf("skill: no skill named %q (run /skills in zut to see what's available)", in.Name)}},
8888 }, nil
8989 }
9090
M packages/agent/skills/tool_test.go +1 -1
@@ -6,7 +6,7 @@ import (
66 "strings"
77 "testing"
88
9- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/provider"
1010 )
1111
1212 func TestToolLoadsSkillByPathAlias(t *testing.T) {
M packages/agent/subagent_worker.go +15 -15
@@ -13,14 +13,14 @@ import (
1313 "time"
1414 "unicode/utf8"
1515
16- "github.com/patriceckhart/zot/packages/agent/modes"
17- "github.com/patriceckhart/zot/packages/agent/subagents"
18- "github.com/patriceckhart/zot/packages/core"
19- "github.com/patriceckhart/zot/packages/provider"
16+ "github.com/bnema/zut/packages/agent/modes"
17+ "github.com/bnema/zut/packages/agent/subagents"
18+ "github.com/bnema/zut/packages/core"
19+ "github.com/bnema/zut/packages/provider"
2020 )
2121
2222 // runSubagentWorkerMode is the daemon-mode entry point used by every
23-// subagent-spawned zot subprocess. It's intentionally close in shape to
23+// subagent-spawned zut subprocess. It's intentionally close in shape to
2424 // runJSONMode but with two key differences:
2525 //
2626 // - Lifetime: the process stays alive across many user turns. The
@@ -28,7 +28,7 @@ import (
2828 // turns arrive through the inbox unix socket at args.SubagentWorker.
2929 //
3030 // - Output: every emitted JSON line is also mirrored verbatim into
31-// events.jsonl (see ZOT_SUBAGENT_EVENT_LOG) so a separate zot
31+// events.jsonl (see ZUT_SUBAGENT_EVENT_LOG) so a separate zut
3232 // process can /subagents open this agent and replay its full history
3333 // even after the parent that spawned us is long gone.
3434 //
@@ -40,7 +40,7 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
4040 if args.SubagentWorker == "" {
4141 return fmt.Errorf("--subagent-worker requires a socket path")
4242 }
43- if os.Getenv("ZOT_SUBAGENT_CREDENTIAL_STDIN") == "1" {
43+ if os.Getenv("ZUT_SUBAGENT_CREDENTIAL_STDIN") == "1" {
4444 var inherited subagents.Credential
4545 dec := json.NewDecoder(io.LimitReader(os.Stdin, 1<<20))
4646 if err := dec.Decode(&inherited); err != nil {
@@ -99,11 +99,11 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
9999
100100 // Event log is owned by the supervisor's runner via stdout, but
101101 // the daemon also writes a redundant copy here when the runner's
102- // pipe is closed (e.g. parent zot exited but the agent is still
102+ // pipe is closed (e.g. parent zut exited but the agent is still
103103 // running headless). The env var is set by the runner; if it's
104104 // empty we silently skip the second mirror.
105105 var logMirror *subagents.EventLog
106- if path := os.Getenv("ZOT_SUBAGENT_EVENT_LOG"); path != "" {
106+ if path := os.Getenv("ZUT_SUBAGENT_EVENT_LOG"); path != "" {
107107 logMirror, _ = subagents.OpenEventLog(path)
108108 }
109109 if logMirror != nil {
@@ -111,7 +111,7 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
111111 }
112112
113113 em := newSubagentEmitter(os.Stdout, logMirror)
114- em.setProtocolIdentity(os.Getenv("ZOT_SUBAGENT_AGENT_ID"))
114+ em.setProtocolIdentity(os.Getenv("ZUT_SUBAGENT_AGENT_ID"))
115115 em.emit("agent.ready", map[string]any{
116116 "version": version,
117117 "cwd": r.CWD,
@@ -125,7 +125,7 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
125125 defer close(heartbeatDone)
126126 go func() {
127127 interval := 10 * time.Second
128- if raw := os.Getenv("ZOT_SUBAGENT_HEARTBEAT_INTERVAL"); raw != "" {
128+ if raw := os.Getenv("ZUT_SUBAGENT_HEARTBEAT_INTERVAL"); raw != "" {
129129 if parsed, parseErr := time.ParseDuration(raw); parseErr == nil && parsed > 0 {
130130 interval = parsed
131131 }
@@ -148,7 +148,7 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
148148 mu sync.Mutex
149149 cancelFn context.CancelFunc
150150 busyTurn bool
151- turnNo = initialTurnNumber(os.Getenv("ZOT_SUBAGENT_EVENT_LOG"))
151+ turnNo = initialTurnNumber(os.Getenv("ZUT_SUBAGENT_EVENT_LOG"))
152152 turnWG sync.WaitGroup
153153 closing bool
154154 shutdown = make(chan struct{})
@@ -390,7 +390,7 @@ func (e *subagentEmitter) emit(typ string, data map[string]any) {
390390
391391 if !e.orphan && err == nil {
392392 if _, werr := e.w.Write(line); werr != nil {
393- // Supervisor's stdout pipe is gone (parent zot exited but we
393+ // Supervisor's stdout pipe is gone (parent zut exited but we
394394 // kept running). Switch to mirror-only mode and preserve this
395395 // event in the durable log.
396396 e.orphan = true
@@ -451,10 +451,10 @@ func boundedResultSummary(text string) string {
451451
452452 func workerOutputLimits() (maxBytes, maxLines int) {
453453 maxBytes, maxLines = 500_000, 5_000
454- if value, err := strconv.Atoi(strings.TrimSpace(os.Getenv("ZOT_SUBAGENT_MAX_OUTPUT_BYTES"))); err == nil && value > 0 {
454+ if value, err := strconv.Atoi(strings.TrimSpace(os.Getenv("ZUT_SUBAGENT_MAX_OUTPUT_BYTES"))); err == nil && value > 0 {
455455 maxBytes = value
456456 }
457- if value, err := strconv.Atoi(strings.TrimSpace(os.Getenv("ZOT_SUBAGENT_MAX_OUTPUT_LINES"))); err == nil && value > 0 {
457+ if value, err := strconv.Atoi(strings.TrimSpace(os.Getenv("ZUT_SUBAGENT_MAX_OUTPUT_LINES"))); err == nil && value > 0 {
458458 maxLines = value
459459 }
460460 return maxBytes, maxLines
M packages/agent/subagent_worker_test.go +6 -6
@@ -9,21 +9,21 @@ import (
99 "strings"
1010 "testing"
1111
12- "github.com/patriceckhart/zot/packages/agent/subagents"
12+ "github.com/bnema/zut/packages/agent/subagents"
1313 )
1414
1515 // TestWorkerOutputLimitsUseSupervisorPolicy verifies that the child reads
1616 // the effective output caps propagated by the supervisor and retains safe
1717 // defaults for standalone workers.
1818 func TestWorkerOutputLimitsUseSupervisorPolicy(t *testing.T) {
19- t.Setenv("ZOT_SUBAGENT_MAX_OUTPUT_BYTES", "123")
20- t.Setenv("ZOT_SUBAGENT_MAX_OUTPUT_LINES", "7")
19+ t.Setenv("ZUT_SUBAGENT_MAX_OUTPUT_BYTES", "123")
20+ t.Setenv("ZUT_SUBAGENT_MAX_OUTPUT_LINES", "7")
2121 bytesLimit, linesLimit := workerOutputLimits()
2222 if bytesLimit != 123 || linesLimit != 7 {
2323 t.Fatalf("worker output limits = (%d, %d), want (123, 7)", bytesLimit, linesLimit)
2424 }
25- t.Setenv("ZOT_SUBAGENT_MAX_OUTPUT_BYTES", "invalid")
26- t.Setenv("ZOT_SUBAGENT_MAX_OUTPUT_LINES", "0")
25+ t.Setenv("ZUT_SUBAGENT_MAX_OUTPUT_BYTES", "invalid")
26+ t.Setenv("ZUT_SUBAGENT_MAX_OUTPUT_LINES", "0")
2727 bytesLimit, linesLimit = workerOutputLimits()
2828 if bytesLimit != 500_000 || linesLimit != 5_000 {
2929 t.Fatalf("invalid worker output limits = (%d, %d), want defaults", bytesLimit, linesLimit)
@@ -35,7 +35,7 @@ func TestWorkerOutputLimitsUseSupervisorPolicy(t *testing.T) {
3535 //
3636 // Symptom: events.jsonl held two copies of every event because the
3737 // child mirrored each event to disk AND the supervisor parsed the
38-// child's stdout and appended each event to disk too. On next zot
38+// child's stdout and appended each event to disk too. On next zut
3939 // launch the replay produced two transcript lines per real one.
4040 //
4141 // Fix invariant: while stdout writes succeed (i.e. the supervisor is
M packages/agent/subagents/agent.go +3 -3
@@ -47,7 +47,7 @@ type Agent struct {
4747 // the effective provider connection settings from the supervisor.
4848 // Empty values inherit whatever the child resolves on its own from
4949 // config / env / flags. All four settings are persisted in meta.json
50- // so Resume keeps using the same provider configuration across zot
50+ // so Resume keeps using the same provider configuration across zut
5151 // restarts.
5252 Model string
5353 Provider string
@@ -58,7 +58,7 @@ type Agent struct {
5858 Subagent string
5959
6060 // SessionID, when non-empty, scopes the agent to a particular
61- // host zot session: the dashboard only surfaces agents whose
61+ // host zut session: the dashboard only surfaces agents whose
6262 // SessionID matches the active session. Empty means "unscoped" and
6363 // applies to agents spawned without a session context such as in tests. Set at
6464 // Spawn time from Supervisor.activeSession and persisted in
@@ -93,7 +93,7 @@ type Agent struct {
9393 // EventLogPath is the durable JSONL event log for this agent.
9494 // The runner appends every well-formed event from the child
9595 // (plus lifecycle events of its own) here. /subagents open in any
96- // zot process reads from this file to replay the full history.
96+ // zut process reads from this file to replay the full history.
9797 EventLogPath string
9898
9999 // SessionPath is the child's persistent session file. Surfaced
M packages/agent/subagents/event_test.go +1 -1
@@ -192,7 +192,7 @@ func TestReadEventLogIgnoresGarbage(t *testing.T) {
192192 // supervisor parsed child stdout and called log.Append(ev), AND the
193193 // child's mirror appended the same event. Two writes per event
194194 // landed in the file in quick succession with near-identical
195-// timestamps, and the next zot launch replayed everything twice —
195+// timestamps, and the next zut launch replayed everything twice —
196196 // the user saw every transcript line duplicated.
197197 //
198198 // The write-time fix makes the child's mirror dormant when the
M packages/agent/subagents/fast_mode_test.go +5 -5
@@ -37,7 +37,7 @@ func TestSupervisorFastModePropagatesToChild(t *testing.T) {
3737 if !a.FastMode {
3838 t.Fatal("spawned agent FastMode = false, want true")
3939 }
40- args := defaultChildArgs("/zot", a, "/session", "/inbox")
40+ args := defaultChildArgs("/zut", a, "/session", "/inbox")
4141 if !containsArg(args, "--fast-mode") {
4242 t.Fatalf("child args = %v, want --fast-mode", args)
4343 }
@@ -91,7 +91,7 @@ func TestSpawnRequestFastModeIsBoundByHostSetting(t *testing.T) {
9191 if a.FastMode != tc.want {
9292 t.Fatalf("FastMode = %v, want %v", a.FastMode, tc.want)
9393 }
94- args := defaultChildArgs("/zot", a, "/session", "/inbox")
94+ args := defaultChildArgs("/zut", a, "/session", "/inbox")
9595 wantFlag := "--no-fast-mode"
9696 if tc.want {
9797 wantFlag = "--fast-mode"
@@ -142,7 +142,7 @@ func TestSupervisorReloadPreservesExplicitFastModeOff(t *testing.T) {
142142 }
143143
144144 func TestSubagentWorkerArgsOmitFastModeWhenUnset(t *testing.T) {
145- args := subagentWorkerArgs(subagentWorkerArgsOpts{Exe: "/zot", Dir: "/wt", SessionPath: "/s", InboxPath: "/i"})
145+ args := subagentWorkerArgs(subagentWorkerArgsOpts{Exe: "/zut", Dir: "/wt", SessionPath: "/s", InboxPath: "/i"})
146146 if strings.Contains(strings.Join(args, " "), "--fast-mode") || strings.Contains(strings.Join(args, " "), "--no-fast-mode") {
147147 t.Fatalf("child args = %v, want fast mode omitted when unset", args)
148148 }
@@ -150,7 +150,7 @@ func TestSubagentWorkerArgsOmitFastModeWhenUnset(t *testing.T) {
150150
151151 func TestSubagentWorkerArgsExplicitlyDisablesFastMode(t *testing.T) {
152152 args := subagentWorkerArgs(subagentWorkerArgsOpts{
153- Exe: "/zot",
153+ Exe: "/zut",
154154 Dir: "/wt",
155155 SessionPath: "/s",
156156 InboxPath: "/i",
@@ -165,7 +165,7 @@ func TestSubagentWorkerArgsExplicitlyDisablesFastMode(t *testing.T) {
165165 }
166166
167167 func TestDefaultChildArgsExplicitlyDisablesFastMode(t *testing.T) {
168- args := defaultChildArgs("/zot", &Agent{Dir: "/wt"}, "/s", "/i")
168+ args := defaultChildArgs("/zut", &Agent{Dir: "/wt"}, "/s", "/i")
169169 if !containsArg(args, "--no-fast-mode") {
170170 t.Fatalf("child args = %v, want --no-fast-mode", args)
171171 }
M packages/agent/subagents/inbox.go +2 -2
@@ -142,7 +142,7 @@ func isNoListenerErr(err error) bool {
142142 // distinguish a live worker from a stale path. Callers use this probe before
143143 // resume/remove operations to avoid racing a detached worker's session and
144144 // workspace ownership.
145-const inboxProbeLine = "__zot_subagent_probe__"
145+const inboxProbeLine = "__zut_subagent_probe__"
146146
147147 func inboxLive(path string) bool {
148148 if path == "" {
@@ -169,7 +169,7 @@ type Listener struct {
169169 path string
170170 ln net.Listener
171171 // active is the most recent accepted connection. The protocol
172- // only expects one supervisor (the parent zot), so newer
172+ // only expects one supervisor (the parent zut), so newer
173173 // connections preempt older ones — the previous parent
174174 // presumably crashed.
175175 mu sync.Mutex
M packages/agent/subagents/inbox_test.go +2 -2
@@ -22,7 +22,7 @@ func shortSocketDir(t *testing.T) string {
2222 if runtime.GOOS == "windows" {
2323 t.Skip("subagent inbox transport uses Unix-domain sockets; Windows support will use a named-pipe backend")
2424 }
25- dir, err := os.MkdirTemp("/tmp", "zot-in-")
25+ dir, err := os.MkdirTemp("/tmp", "zut-in-")
2626 if err != nil {
2727 t.Fatalf("mkdir: %v", err)
2828 }
@@ -119,7 +119,7 @@ func TestIsNoListenerErrMatchesECONNREFUSED(t *testing.T) {
119119
120120 // TestInboxNewSupervisorPreemptsOld documents that only one
121121 // supervisor connection at a time is honoured. The subagent design
122-// assumes a single parent zot owns each agent; if a second
122+// assumes a single parent zut owns each agent; if a second
123123 // parent dials, the listener boots the first one. The first
124124 // supervisor's already-delivered messages still land in the
125125 // channel — what we're guarding against is silent dual ownership,
M packages/agent/subagents/persist.go +5 -5
@@ -5,7 +5,7 @@ package subagents
55 // Every Spawn writes a meta.json next to the agent's events.jsonl and
66 // session.json. The file captures the immutable identity bits (id,
77 // task, branch, dir) plus the paths the runner needs to resume the
8-// agent later. On a fresh zot launch, Supervisor.Reload() walks
8+// agent later. On a fresh zut launch, Supervisor.Reload() walks
99 // <root>/agents/*/meta.json and re-registers every agent it finds in
1010 // StatusDetached so the user can see, view, resume, or remove them
1111 // from the dashboard.
@@ -76,7 +76,7 @@ type agentMeta struct {
7676 SessionPath string `json:"session_path"`
7777
7878 // SessionID, when non-empty, scopes the agent to a particular
79- // host zot session: the dashboard only shows agents whose
79+ // host zut session: the dashboard only shows agents whose
8080 // SessionID matches the active session. Older meta.json files
8181 // (and agents spawned outside of any session, e.g. by tests or
8282 // scripted callers that didn't call SetActiveSession) have an
@@ -218,7 +218,7 @@ func validRuntimeInboxPath(path, id string) bool {
218218 return false
219219 }
220220 dir := filepath.Dir(path)
221- if !strings.HasPrefix(filepath.Base(dir), "zot-subagents-") {
221+ if !strings.HasPrefix(filepath.Base(dir), "zut-subagents-") {
222222 return false
223223 }
224224 bases := []string{os.TempDir(), "/tmp"}
@@ -558,7 +558,7 @@ func replayEventsIntoAgent(a *Agent, evs []Event) {
558558 // Resume re-attaches a Runner to a previously-spawned agent. Durable
559559 // session and event files are kept, while the transient inbox path is
560560 // recalculated for the current runtime environment. Use this to
561-// continue a subagent session across zot restarts:
561+// continue a subagent session across zut restarts:
562562 //
563563 // subagentMgr.Reload()
564564 // a, err := subagentMgr.Resume(ctx, "alpha-12345")
@@ -627,7 +627,7 @@ func (f *Supervisor) resume(ctx context.Context, id string, resuming bool) (*Age
627627 // Rebuild from the meta record so we don't carry stale runner
628628 // state from a previous incarnation. The inbox is transient and
629629 // may have been persisted under an incompatible filesystem by an
630- // older zot version, so always select it again on resume.
630+ // older zut version, so always select it again on resume.
631631 inboxPath, err := inboxSocketPath(f.cfg.Root, existing.ID)
632632 if err != nil {
633633 return nil, fmt.Errorf("subagent inbox path: %w", err)
M packages/agent/subagents/persist_test.go +5 -5
@@ -16,7 +16,7 @@ import (
1616
1717 // TestSpawnWritesMetaJSON asserts the durability contract: every
1818 // successful Spawn leaves a meta.json on disk with the agent's
19-// identity bits. Without this, Reload on the next zot launch can't
19+// identity bits. Without this, Reload on the next zut launch can't
2020 // find the agent and the user loses access to the worktree.
2121 func TestSpawnWritesMetaJSON(t *testing.T) {
2222 root := t.TempDir()
@@ -63,7 +63,7 @@ func TestSpawnWritesMetaJSON(t *testing.T) {
6363 }
6464 }
6565
66-// TestReloadRebuildsDetachedAgents simulates a zot restart by spawning
66+// TestReloadRebuildsDetachedAgents simulates a zut restart by spawning
6767 // in one Supervisor, throwing it away, then opening a fresh Supervisor against
6868 // the same root and calling Reload. The user-visible state — id,
6969 // task, branch, dir — must come back, and status must be Detached so
@@ -873,7 +873,7 @@ func TestSpawnReqPersistsModel(t *testing.T) {
873873 // updating this assertion is the failure mode this catches.
874874 func TestSubagentWorkerArgsIncludesModelFlags(t *testing.T) {
875875 args := subagentWorkerArgs(subagentWorkerArgsOpts{
876- Exe: "/zot", Dir: "/wt", SessionPath: "/s.json", InboxPath: "/in.sock",
876+ Exe: "/zut", Dir: "/wt", SessionPath: "/s.json", InboxPath: "/in.sock",
877877 Task: "do x", Model: "gpt-5", Provider: "openai",
878878 })
879879 want := []string{"--model", "gpt-5", "--provider", "openai"}
@@ -1089,7 +1089,7 @@ func TestActiveSessionScopesSnapshotAll(t *testing.T) {
10891089 // Supervisor instance is still SessionID="sess-A" after a fresh New +
10901090 // Reload reads it back from meta.json. Without persistence, the
10911091 // scope filter would forget which session owned each agent after
1092-// a zot restart and the dashboard would leak everything again.
1092+// a zut restart and the dashboard would leak everything again.
10931093 func TestSessionIDPersistsAcrossReload(t *testing.T) {
10941094 root := t.TempDir()
10951095 mkSupervisor := func() *Supervisor {
@@ -1134,7 +1134,7 @@ func TestSessionIDPersistsAcrossReload(t *testing.T) {
11341134 // were spawned without an active session, e.g. via a test rig or
11351135 // scripted caller) carry SessionID == "" and remain visible from
11361136 // every scope. Otherwise the schema bump would orphan every
1137-// pre-existing agent the moment a user upgraded zot.
1137+// pre-existing agent the moment a user upgraded zut.
11381138 func TestEmptySessionIDIsVisibleFromAnyScope(t *testing.T) {
11391139 root := t.TempDir()
11401140 f := New(Config{
M packages/agent/subagents/process_unix.go +1 -1
@@ -39,7 +39,7 @@ func closeWorkerPipes(pipes []io.Closer) {
3939 }
4040
4141 // setProcessGroup puts a worker in a process group of its own. This keeps a
42-// canceled worker from sharing a group with the supervising zot process.
42+// canceled worker from sharing a group with the supervising zut process.
4343 func setProcessGroup(cmd *exec.Cmd) {
4444 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
4545 }
M packages/agent/subagents/profiles.go +3 -3
@@ -1,5 +1,5 @@
11 // Package subagents discovers named agent profiles that can be selected by
2-// zot's subagent supervisor. Profiles use the common markdown/frontmatter layout:
2+// zut's subagent supervisor. Profiles use the common markdown/frontmatter layout:
33 // a YAML-like metadata block followed by the agent's system prompt.
44 //
55 // Discovery prefers explicitly configured directories, then the shared
@@ -180,7 +180,7 @@ func searchDirs(cwd, userHome string) []location {
180180 out = append(out, location{dir: dir, label: label})
181181 }
182182 }
183- if extra := os.Getenv("ZOT_AGENT_PROFILES"); extra != "" {
183+ if extra := os.Getenv("ZUT_AGENT_PROFILES"); extra != "" {
184184 for _, dir := range filepath.SplitList(extra) {
185185 dir = strings.TrimSpace(dir)
186186 if dir != "" && !filepath.IsAbs(dir) && strings.TrimSpace(cwd) != "" {
@@ -192,7 +192,7 @@ func searchDirs(cwd, userHome string) []location {
192192 if userHome != "" {
193193 add(filepath.Join(userHome, ".agents", "agents"), "global (agents)")
194194 // This is an additional compatibility location. The shared .agents
195- // path above remains zot's documented default.
195+ // path above remains zut's documented default.
196196 add(filepath.Join(userHome, ".pi", "agent", "agents"), "global (pi)")
197197 }
198198 return out
M packages/agent/subagents/profiles_test.go +2 -2
@@ -79,7 +79,7 @@ Implement the requested change.
7979 func TestDiscoverResolvesConfiguredRelativePathAgainstCWD(t *testing.T) {
8080 first := t.TempDir()
8181 second := t.TempDir()
82- t.Setenv("ZOT_AGENT_PROFILES", ".agents")
82+ t.Setenv("ZUT_AGENT_PROFILES", ".agents")
8383 writeProfile(t, filepath.Join(first, ".agents", "reviewer.md"), `---
8484 name: reviewer
8585 description: First session profile
@@ -113,7 +113,7 @@ Second.
113113 func TestDiscoverConfiguredDirectoryWinsAndRejectsUnsafeNames(t *testing.T) {
114114 configured := t.TempDir()
115115 home := t.TempDir()
116- t.Setenv("ZOT_AGENT_PROFILES", configured)
116+ t.Setenv("ZUT_AGENT_PROFILES", configured)
117117 writeProfile(t, filepath.Join(configured, "reviewer.md"), `---
118118 name: reviewer
119119 description: Configured reviewer
M packages/agent/subagents/runner.go +18 -18
@@ -14,11 +14,11 @@ import (
1414 "time"
1515 )
1616
17-// execRunner spawns `zot --subagent-worker <inbox> --session <path>` in
17+// execRunner spawns `zut --subagent-worker <inbox> --session <path>` in
1818 // Agent.Dir (the shared repository root or an isolated worktree) and consumes
1919 // its JSONL event stream on stdout.
2020 //
21-// Why a long-lived daemon and not `zot --print`: the supervisor and
21+// Why a long-lived daemon and not `zut --print`: the supervisor and
2222 // the user expect agents to keep accepting follow-up prompts. A
2323 // one-shot subprocess can't do that; this design gives each subagent
2424 // agent a persistent session file plus an inbox socket the parent
@@ -31,13 +31,13 @@ import (
3131 //
3232 // The on-disk log is the durable record. The Sink updates are an
3333 // in-memory mirror so the dashboard doesn't have to tail the file
34-// for the parent's own agents. /subagents open in a separate zot would
34+// for the parent's own agents. /subagents open in a separate zut would
3535 // read the log directly.
3636 type execRunner struct {
3737 agent *Agent
3838 resolveCredential func(context.Context, string) (Credential, error)
3939
40- // Command overrides the default `zot --subagent-worker ...`
40+ // Command overrides the default `zut --subagent-worker ...`
4141 // invocation. Tests set this to a fake binary (or `go run`
4242 // against a tiny stub program) so the supervisor logic can be
4343 // tested without a real child. Production code leaves it nil.
@@ -48,7 +48,7 @@ type execRunner struct {
4848 // with <subagent-root>/agents/<id>/session.json. Tests that
4949 // hand-build an Agent without going through Spawn must set
5050 // one of the two; the runner refuses to invent a fallback
51- // because the only plausible one (<Dir>/.zot/session.json)
51+ // because the only plausible one (<Dir>/.zut/session.json)
5252 // would litter the user's repo — every agent's Dir points
5353 // at it directly.
5454 SessionPath string
@@ -57,7 +57,7 @@ type execRunner struct {
5757 // subagentWorkerArgsOpts captures every dynamic input to subagentWorkerArgs.
5858 // The fields map 1:1 onto child CLI flags; empty values omit the flag
5959 // entirely and let the child resolve a default the same way a normal
60-// `zot` invocation does.
60+// `zut` invocation does.
6161 type subagentWorkerArgsOpts struct {
6262 Exe string
6363 Dir string
@@ -164,7 +164,7 @@ func subagentWorkerArgs(opts subagentWorkerArgsOpts) []string {
164164 return args
165165 }
166166
167-// resolveSubagentExecutable finds a runnable zot binary without assuming a
167+// resolveSubagentExecutable finds a runnable zut binary without assuming a
168168 // particular installation directory. A parent process can outlive the file
169169 // it was started from (for example after a reinstall), so all known
170170 // candidates are checked concurrently and the first successful lookup wins.
@@ -207,10 +207,10 @@ func resolveSubagentExecutable(ctx context.Context, self, argv0 string, lookPath
207207 if argv0 != "" {
208208 addCandidate(filepath.Base(argv0))
209209 }
210- addCandidate("zot")
210+ addCandidate("zut")
211211
212212 if len(candidates) == 0 {
213- return "", fmt.Errorf("locate zot executable: no executable candidates")
213+ return "", fmt.Errorf("locate zut executable: no executable candidates")
214214 }
215215
216216 type lookupResult struct {
@@ -240,7 +240,7 @@ func resolveSubagentExecutable(ctx context.Context, self, argv0 string, lookPath
240240 }
241241 lookupErrors = append(lookupErrors, fmt.Sprintf("%q: %v", result.candidate, result.err))
242242 }
243- return "", fmt.Errorf("locate zot executable: %s", strings.Join(lookupErrors, "; "))
243+ return "", fmt.Errorf("locate zut executable: %s", strings.Join(lookupErrors, "; "))
244244 }
245245
246246 func (r *execRunner) Run(ctx context.Context, sink Sink) error {
@@ -251,7 +251,7 @@ func (r *execRunner) Run(ctx context.Context, sink Sink) error {
251251 // <subagent-root>/agents/<id>/session.json so the per-
252252 // agent state is entirely outside the working tree.
253253 // Crucial because Agent.Dir points at the user's repo;
254- // any .zot/ scratch directory under Dir would litter
254+ // any .zut/ scratch directory under Dir would litter
255255 // their source tree.
256256 //
257257 // There is no third fallback. If neither path is set we
@@ -291,7 +291,7 @@ func (r *execRunner) Run(ctx context.Context, sink Sink) error {
291291 exe, err := resolveSubagentExecutable(ctx, self, argv0, nil)
292292 if err != nil {
293293 if selfErr != nil {
294- return fmt.Errorf("locate zot executable (os.Executable: %v): %w", selfErr, err)
294+ return fmt.Errorf("locate zut executable (os.Executable: %v): %w", selfErr, err)
295295 }
296296 return err
297297 }
@@ -301,17 +301,17 @@ func (r *execRunner) Run(ctx context.Context, sink Sink) error {
301301 cmd := exec.CommandContext(ctx, args[0], args[1:]...)
302302 cmd.Dir = r.agent.Dir
303303 cmd.Env = append(workerEnvironment(r.agent.Provider),
304- "ZOT_SUBAGENT_AGENT_ID="+r.agent.ID,
305- "ZOT_SUBAGENT_EVENT_LOG="+logPath,
304+ "ZUT_SUBAGENT_AGENT_ID="+r.agent.ID,
305+ "ZUT_SUBAGENT_EVENT_LOG="+logPath,
306306 )
307307 if r.agent.HeartbeatInterval > 0 {
308- cmd.Env = append(cmd.Env, "ZOT_SUBAGENT_HEARTBEAT_INTERVAL="+r.agent.HeartbeatInterval.String())
308+ cmd.Env = append(cmd.Env, "ZUT_SUBAGENT_HEARTBEAT_INTERVAL="+r.agent.HeartbeatInterval.String())
309309 }
310310 if r.agent.maxOutputBytes > 0 {
311- cmd.Env = append(cmd.Env, fmt.Sprintf("ZOT_SUBAGENT_MAX_OUTPUT_BYTES=%d", r.agent.maxOutputBytes))
311+ cmd.Env = append(cmd.Env, fmt.Sprintf("ZUT_SUBAGENT_MAX_OUTPUT_BYTES=%d", r.agent.maxOutputBytes))
312312 }
313313 if r.agent.maxOutputLines > 0 {
314- cmd.Env = append(cmd.Env, fmt.Sprintf("ZOT_SUBAGENT_MAX_OUTPUT_LINES=%d", r.agent.maxOutputLines))
314+ cmd.Env = append(cmd.Env, fmt.Sprintf("ZUT_SUBAGENT_MAX_OUTPUT_LINES=%d", r.agent.maxOutputLines))
315315 }
316316 if r.resolveCredential != nil {
317317 credential, resolveErr := r.resolveCredential(ctx, r.agent.Provider)
@@ -324,7 +324,7 @@ func (r *execRunner) Run(ctx context.Context, sink Sink) error {
324324 return fmt.Errorf("encode subagent credential: %w", encodeErr)
325325 }
326326 cmd.Stdin = bytes.NewReader(encoded)
327- cmd.Env = append(cmd.Env, "ZOT_SUBAGENT_CREDENTIAL_STDIN=1")
327+ cmd.Env = append(cmd.Env, "ZUT_SUBAGENT_CREDENTIAL_STDIN=1")
328328 }
329329 }
330330
M packages/agent/subagents/runner_e2e_test.go +1 -1
@@ -40,7 +40,7 @@ func TestRunnerEndToEndWithStubChild(t *testing.T) {
4040 }
4141
4242 exe := buildStubChild(t)
43- t.Setenv("ZOT_STUB_PROTOCOL", "1")
43+ t.Setenv("ZUT_STUB_PROTOCOL", "1")
4444
4545 root := t.TempDir()
4646 repo := t.TempDir()
M packages/agent/subagents/runner_test.go +25 -25
@@ -20,7 +20,7 @@ import (
2020 // talk to the model.
2121 //
2222 // - Forgetting --cwd: the child resolved tools against the parent
23-// zot's working directory, defeating the whole point of the
23+// zut's working directory, defeating the whole point of the
2424 // worktree isolation.
2525 //
2626 // - Forgetting --session: a daemon-mode agent without a session
@@ -31,10 +31,10 @@ import (
3131 // positions. If a flag is renamed, update both the runner and this
3232 // test so we notice immediately.
3333 func TestCredentialStdinHelperProcess(t *testing.T) {
34- if os.Getenv("ZOT_SUBAGENT_CREDENTIAL_HELPER") != "1" {
34+ if os.Getenv("ZUT_SUBAGENT_CREDENTIAL_HELPER") != "1" {
3535 return
3636 }
37- if os.Getenv("ZOT_SUBAGENT_CREDENTIAL_STDIN") != "1" {
37+ if os.Getenv("ZUT_SUBAGENT_CREDENTIAL_STDIN") != "1" {
3838 os.Exit(2)
3939 }
4040 var credential Credential
@@ -67,7 +67,7 @@ func TestWorkerEnvironmentRedactsProviderSecrets(t *testing.T) {
6767 t.Setenv("CUSTOM_PROVIDER_KEY", "key-secret")
6868 t.Setenv("AWS_ACCESS_KEY_ID", "aws-access-secret")
6969 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/service-account.json")
70- t.Setenv("ZOT_SUBAGENT_TEST_VALUE", "not-secret")
70+ t.Setenv("ZUT_SUBAGENT_TEST_VALUE", "not-secret")
7171 env := strings.Join(workerEnvironment("openai"), "\n")
7272 for _, secret := range []string{
7373 "OPENAI_API_KEY=openai-secret",
@@ -83,7 +83,7 @@ func TestWorkerEnvironmentRedactsProviderSecrets(t *testing.T) {
8383 t.Fatalf("worker environment leaked %s", secret)
8484 }
8585 }
86- if !strings.Contains(env, "ZOT_SUBAGENT_TEST_VALUE=not-secret") {
86+ if !strings.Contains(env, "ZUT_SUBAGENT_TEST_VALUE=not-secret") {
8787 t.Fatal("worker environment dropped unrelated variables")
8888 }
8989
@@ -98,7 +98,7 @@ func TestWorkerEnvironmentRedactsProviderSecrets(t *testing.T) {
9898 }
9999
100100 func TestExecRunnerTransfersCredentialOnlyOnStdin(t *testing.T) {
101- t.Setenv("ZOT_SUBAGENT_CREDENTIAL_HELPER", "1")
101+ t.Setenv("ZUT_SUBAGENT_CREDENTIAL_HELPER", "1")
102102 root := t.TempDir()
103103 a := &Agent{
104104 ID: "credential-test",
@@ -147,7 +147,7 @@ func TestApplyEventPreservesMultilineRolesAndFinalAssistant(t *testing.T) {
147147
148148 func TestSubagentWorkerArgs(t *testing.T) {
149149 args := subagentWorkerArgs(subagentWorkerArgsOpts{
150- Exe: "/path/to/zot",
150+ Exe: "/path/to/zut",
151151 Dir: "/tmp/worktree",
152152 SessionPath: "/tmp/state/session.json",
153153 InboxPath: "/tmp/state/in.sock",
@@ -158,7 +158,7 @@ func TestSubagentWorkerArgs(t *testing.T) {
158158 if len(args) < 7 {
159159 t.Fatalf("argv unexpectedly short: %v", args)
160160 }
161- if args[0] != "/path/to/zot" {
161+ if args[0] != "/path/to/zut" {
162162 t.Fatalf("argv[0] = %q; want the binary path", args[0])
163163 }
164164 // The task must come last so anything that looks flag-like in
@@ -195,8 +195,8 @@ func TestSubagentWorkerArgs(t *testing.T) {
195195 }
196196
197197 func TestResolveSubagentExecutableUsesCurrentWhenAvailable(t *testing.T) {
198- got, err := resolveSubagentExecutable(context.Background(), "/current/bin/zot", "/old/bin/zot", func(_ context.Context, candidate string) (string, error) {
199- if candidate == "/current/bin/zot" {
198+ got, err := resolveSubagentExecutable(context.Background(), "/current/bin/zut", "/old/bin/zut", func(_ context.Context, candidate string) (string, error) {
199+ if candidate == "/current/bin/zut" {
200200 return candidate, nil
201201 }
202202 return "", os.ErrNotExist
@@ -204,22 +204,22 @@ func TestResolveSubagentExecutableUsesCurrentWhenAvailable(t *testing.T) {
204204 if err != nil {
205205 t.Fatal(err)
206206 }
207- if got != "/current/bin/zot" {
207+ if got != "/current/bin/zut" {
208208 t.Fatalf("executable = %q, want current executable", got)
209209 }
210210 }
211211
212212 func TestResolveSubagentExecutableFallsBackToPath(t *testing.T) {
213- got, err := resolveSubagentExecutable(context.Background(), "/removed/.local/bin/zot", "/removed/.local/bin/zot", func(_ context.Context, candidate string) (string, error) {
214- if candidate == "zot" {
215- return "/active/go/bin/zot", nil
213+ got, err := resolveSubagentExecutable(context.Background(), "/removed/.local/bin/zut", "/removed/.local/bin/zut", func(_ context.Context, candidate string) (string, error) {
214+ if candidate == "zut" {
215+ return "/active/go/bin/zut", nil
216216 }
217217 return "", os.ErrNotExist
218218 })
219219 if err != nil {
220220 t.Fatal(err)
221221 }
222- if got != "/active/go/bin/zot" {
222+ if got != "/active/go/bin/zut" {
223223 t.Fatalf("executable = %q, want PATH fallback", got)
224224 }
225225 }
@@ -227,16 +227,16 @@ func TestResolveSubagentExecutableFallsBackToPath(t *testing.T) {
227227 func TestResolveSubagentExecutableCancelsOtherLookupsAfterFirstSuccess(t *testing.T) {
228228 slowStarted := make(chan struct{})
229229 slowCanceled := make(chan struct{})
230- got, err := resolveSubagentExecutable(context.Background(), "/current/bin/zot", "/old/bin/zot", func(ctx context.Context, candidate string) (string, error) {
230+ got, err := resolveSubagentExecutable(context.Background(), "/current/bin/zut", "/old/bin/zut", func(ctx context.Context, candidate string) (string, error) {
231231 switch candidate {
232- case "/current/bin/zot":
232+ case "/current/bin/zut":
233233 close(slowStarted)
234234 <-ctx.Done()
235235 close(slowCanceled)
236236 return "", ctx.Err()
237- case "zot":
237+ case "zut":
238238 <-slowStarted
239- return "/active/go/bin/zot", nil
239+ return "/active/go/bin/zut", nil
240240 default:
241241 return "", os.ErrNotExist
242242 }
@@ -244,7 +244,7 @@ func TestResolveSubagentExecutableCancelsOtherLookupsAfterFirstSuccess(t *testin
244244 if err != nil {
245245 t.Fatal(err)
246246 }
247- if got != "/active/go/bin/zot" {
247+ if got != "/active/go/bin/zut" {
248248 t.Fatalf("executable = %q, want first successful lookup", got)
249249 }
250250 select {
@@ -256,7 +256,7 @@ func TestResolveSubagentExecutableCancelsOtherLookupsAfterFirstSuccess(t *testin
256256
257257 func TestSubagentWorkerArgsPropagatesProviderConnectionSettings(t *testing.T) {
258258 args := subagentWorkerArgs(subagentWorkerArgsOpts{
259- Exe: "/zot",
259+ Exe: "/zut",
260260 Dir: "/work",
261261 SessionPath: "/state/session.json",
262262 InboxPath: "/state/inbox.sock",
@@ -282,7 +282,7 @@ func TestDefaultChildArgsPropagatesProviderConnectionSettings(t *testing.T) {
282282 BaseURL: "https://gateway.example.test/v1",
283283 InsecureTLS: true,
284284 }
285- args := defaultChildArgs("/zot", a, "/state/session.json", "/state/inbox.sock")
285+ args := defaultChildArgs("/zut", a, "/state/session.json", "/state/inbox.sock")
286286 if i := indexOf(args, "--base-url"); i < 0 || safeAt(args, i+1) != a.BaseURL {
287287 t.Fatalf("argv = %v, want Agent.BaseURL propagated", args)
288288 }
@@ -296,7 +296,7 @@ func TestDefaultChildArgsPropagatesProviderConnectionSettings(t *testing.T) {
296296 // positional which the arg parser would treat as a real prompt.
297297 func TestSubagentWorkerArgsEmptyTaskOmitsPositional(t *testing.T) {
298298 args := subagentWorkerArgs(subagentWorkerArgsOpts{
299- Exe: "/zot", Dir: "/wt", SessionPath: "/s.json", InboxPath: "/in.sock",
299+ Exe: "/zut", Dir: "/wt", SessionPath: "/s.json", InboxPath: "/in.sock",
300300 })
301301 for _, a := range args {
302302 if a == "" {
@@ -320,7 +320,7 @@ func TestSubagentWorkerArgsEmptyTaskOmitsPositional(t *testing.T) {
320320 // initial user turn.
321321 func TestDefaultChildArgsSpawnIncludesTask(t *testing.T) {
322322 a := &Agent{Dir: "/wt", Task: "do thing"}
323- args := defaultChildArgs("/zot", a, "/s.json", "/in.sock")
323+ args := defaultChildArgs("/zut", a, "/s.json", "/in.sock")
324324 if got := args[len(args)-1]; got != "do thing" {
325325 t.Fatalf("spawn argv last = %q; want %q\n%v", got, "do thing", args)
326326 }
@@ -334,7 +334,7 @@ func TestDefaultChildArgsSpawnIncludesTask(t *testing.T) {
334334 // user types next via the inbox.
335335 func TestDefaultChildArgsResumeOmitsTask(t *testing.T) {
336336 a := &Agent{Dir: "/wt", Task: "do thing", Resuming: true}
337- args := defaultChildArgs("/zot", a, "/s.json", "/in.sock")
337+ args := defaultChildArgs("/zut", a, "/s.json", "/in.sock")
338338 for _, v := range args {
339339 if v == "do thing" {
340340 t.Fatalf("resume argv contains the task; it would re-fire as a duplicate turn\n%v", args)
M packages/agent/subagents/socketpath.go +3 -3
@@ -29,7 +29,7 @@ const maxUnixSocketPath = 100
2929 // 3. /tmp as a Unix fallback.
3030 //
3131 // Each candidate is probed with a real unix listener before it is selected.
32-// The root hash keeps separate zot homes from colliding while preserving a
32+// The root hash keeps separate zut homes from colliding while preserving a
3333 // stable path across parent and child processes and across Resume calls.
3434 func inboxSocketPath(root, agentID string) (string, error) {
3535 if runtime.GOOS == "windows" {
@@ -65,7 +65,7 @@ func inboxSocketPath(root, agentID string) (string, error) {
6565 }
6666
6767 func socketPathInBase(base, root, agentID string) (string, error) {
68- dir := filepath.Join(base, "zot-subagents-"+rootTag(root))
68+ dir := filepath.Join(base, "zut-subagents-"+rootTag(root))
6969 if err := os.MkdirAll(dir, 0o700); err != nil {
7070 return "", fmt.Errorf("socket dir %s: %w", dir, err)
7171 }
@@ -110,7 +110,7 @@ func probeUnixSocket(dir string) error {
110110 }
111111
112112 // rootTag returns a stable 8-hex-char tag for the subagent root. Used
113-// in the runtime-directory name so two parallel zot instances with
113+// in the runtime-directory name so two parallel zut instances with
114114 // different roots don't share sockets.
115115 func rootTag(root string) string { return shortHash(root) }
116116
M packages/agent/subagents/supervisor.go +7 -7
@@ -1,13 +1,13 @@
1-// Package subagents implements zot's subagent supervisor.
1+// Package subagents implements zut's subagent supervisor.
22 //
3-// A Supervisor manages headless, long-lived zot subprocesses and keeps their
3+// A Supervisor manages headless, long-lived zut subprocesses and keeps their
44 // process/turn state, event logs, sessions, results, and optional worktree
55 // artifacts durable across supervisor restarts.
66 //
77 // Shared workspaces preserve the historical behavior, while an explicit
88 // worktree request gives a child an isolated checkout whose patch is captured
99 // before cleanup. The Runner abstraction lets tests inject a fake process;
10-// production uses `zot --subagent-worker ...`.
10+// production uses `zut --subagent-worker ...`.
1111 package subagents
1212
1313 import (
@@ -43,7 +43,7 @@ type Config struct {
4343 Context context.Context
4444
4545 // Root is the directory under which per-agent state files live.
46- // Typically <ZotHome>/subagents, but tests pass a tempdir.
46+ // Typically <ZutHome>/subagents, but tests pass a tempdir.
4747 Root string
4848
4949 // Policy owns concurrency, timeout, output, and path safety limits.
@@ -70,7 +70,7 @@ type Config struct {
7070 InsecureTLS bool
7171
7272 // NewRunner produces the Runner for an Agent. If nil, the default
73- // `zot --subagent-worker ...` exec runner is used. Tests inject a fake
73+ // `zut --subagent-worker ...` exec runner is used. Tests inject a fake
7474 // here.
7575 NewRunner func(a *Agent) Runner
7676
@@ -254,7 +254,7 @@ func (f *Supervisor) ProviderSettings() (string, bool) {
254254 }
255255
256256 // SetActiveSession scopes the dashboard view (and Spawn stamping)
257-// to a particular host zot session id. Pass empty to clear the
257+// to a particular host zut session id. Pass empty to clear the
258258 // scope and revert to "show every agent" (the original behaviour).
259259 //
260260 // Existing in-memory agents keep their SessionID; only the filter
@@ -422,7 +422,7 @@ func (f *Supervisor) SpawnReq(ctx context.Context, req SpawnRequest) (*Agent, er
422422 logPath := filepath.Join(stateDir, "events.jsonl")
423423 sessionPath := filepath.Join(stateDir, "session.json")
424424 // Keep the transient unix socket outside the durable state root.
425- // ZOT_HOME may live on a shared or network filesystem that supports
425+ // ZUT_HOME may live on a shared or network filesystem that supports
426426 // regular state files but cannot host unix socket nodes.
427427 inboxPath, err := inboxSocketPath(f.cfg.Root, id)
428428 if err != nil {
M packages/agent/subagents/supervisor_test.go +1 -1
@@ -63,7 +63,7 @@ func TestSpawnRunsAndCompletes(t *testing.T) {
6363 }
6464
6565 // TestSpawnAgentSharesRepoRoot verifies the only-mode-we-support:
66-// every spawned agent points its cwd at the parent zot's RepoRoot.
66+// every spawned agent points its cwd at the parent zut's RepoRoot.
6767 func TestSpawnAgentSharesRepoRoot(t *testing.T) {
6868 f := newTestSupervisor(t, func(a *Agent) Runner {
6969 return RunnerFunc(func(ctx context.Context, sink Sink) error { return nil })
M packages/agent/subagents/testdata/cmd/stubchild/main.go +4 -4
@@ -1,5 +1,5 @@
11 // stubchild is a minimal subagent-worker stand-in used by the runner
2-// end-to-end test. It speaks the daemon protocol the real zot
2+// end-to-end test. It speaks the daemon protocol the real zut
33 // binary will implement next:
44 //
55 // - parses --subagent-worker <path>, --session <path>, --cwd <path>,
@@ -14,7 +14,7 @@
1414 //
1515 // The runner test compiles this binary into a tempdir, points
1616 // subagent's execRunner at it via Command, and asserts the events
17-// flow through correctly without needing the full zot model
17+// flow through correctly without needing the full zut model
1818 // machinery.
1919 package main
2020
@@ -28,7 +28,7 @@ import (
2828 "sync"
2929 "time"
3030
31- "github.com/patriceckhart/zot/packages/agent/subagents"
31+ "github.com/bnema/zut/packages/agent/subagents"
3232 )
3333
3434 func main() {
@@ -134,7 +134,7 @@ type emitter = func(string, map[string]any)
134134 func newEmitter() emitter {
135135 var mu sync.Mutex
136136 enc := json.NewEncoder(os.Stdout)
137- versioned := os.Getenv("ZOT_STUB_PROTOCOL") == "1"
137+ versioned := os.Getenv("ZUT_STUB_PROTOCOL") == "1"
138138 return func(typ string, data map[string]any) {
139139 mu.Lock()
140140 defer mu.Unlock()
M packages/agent/systemprompt.go +8 -8
@@ -24,7 +24,7 @@ type SystemPromptOpts struct {
2424 Custom string // if set, replaces the default identity entirely
2525 Append []string // extra text appended at the end
2626 Now time.Time
27- ZotDocsDir string
27+ ZutDocsDir string
2828 }
2929
3030 // BuildSystemPrompt constructs the system prompt.
@@ -33,7 +33,7 @@ type SystemPromptOpts struct {
3333 // the cached prefix on every request, so bloat is cumulatively
3434 // expensive. We ship only:
3535 //
36-// - A one-paragraph identity (who zot is, what the name means,
36+// - A one-paragraph identity (who zut is, what the name means,
3737 // what the TUI expects for output format).
3838 // - The date + cwd footer so the model has current-context.
3939 //
@@ -45,7 +45,7 @@ type SystemPromptOpts struct {
4545 //
4646 // Users who want extra biasing can use --system-prompt (replace),
4747 // --append-system-prompt (additive, repeatable), or drop a
48-// SYSTEM.md in $ZOT_HOME that overrides the default identity.
48+// SYSTEM.md in $ZUT_HOME that overrides the default identity.
4949 func BuildSystemPrompt(o SystemPromptOpts) string {
5050 if o.Now.IsZero() {
5151 o.Now = time.Now()
@@ -64,10 +64,10 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
6464 sb.WriteString(defaultIdentity)
6565 }
6666
67- if strings.TrimSpace(o.ZotDocsDir) != "" {
68- sb.WriteString("\n\nZot's own docs are installed under ")
69- sb.WriteString(o.ZotDocsDir)
70- sb.WriteString("; use the read tool there when you need details about zot RPC, extensions, skills, or built-in behaviour.")
67+ if strings.TrimSpace(o.ZutDocsDir) != "" {
68+ sb.WriteString("\n\nZut's own docs are installed under ")
69+ sb.WriteString(o.ZutDocsDir)
70+ sb.WriteString("; use the read tool there when you need details about zut RPC, extensions, skills, or built-in behaviour.")
7171 }
7272
7373 for _, a := range o.Append {
@@ -82,7 +82,7 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
8282 return sb.String()
8383 }
8484
85-const defaultIdentity = `You are an expert coding assistant operating inside zot, a coding agent harness. The name "zot" stands for "zero-overhead-tool"; if the user asks what zot means, answer exactly that.
85+const defaultIdentity = `You are an expert coding assistant operating inside zut, a coding agent harness. The name "zut" stands for "zero-overhead-tool"; if the user asks what zut means, answer exactly that.
8686
8787 Your output renders in a TUI that understands markdown for prose and plain text for tool output. Use markdown freely, keep answers concise, and let tool calls speak for themselves rather than narrating them in prose before you invoke them. Act first, then summarise what you did.
8888
M packages/agent/tools/bash.go +3 -3
@@ -15,8 +15,8 @@ import (
1515 "strings"
1616 "time"
1717
18- "github.com/patriceckhart/zot/packages/core"
19- "github.com/patriceckhart/zot/packages/provider"
18+ "github.com/bnema/zut/packages/core"
19+ "github.com/bnema/zut/packages/provider"
2020 )
2121
2222 const (
@@ -224,7 +224,7 @@ func humanDuration(d time.Duration) string {
224224 func writeFullOutput(s string) string {
225225 b := make([]byte, 6)
226226 _, _ = rand.Read(b)
227- name := filepath.Join(os.TempDir(), "zot-bash-"+hex.EncodeToString(b)+".log")
227+ name := filepath.Join(os.TempDir(), "zut-bash-"+hex.EncodeToString(b)+".log")
228228 if err := os.WriteFile(name, []byte(s), 0o600); err != nil {
229229 return ""
230230 }
M packages/agent/tools/edit.go +3 -3
@@ -8,9 +8,9 @@ import (
88 "os"
99 "strings"
1010
11- "github.com/patriceckhart/zot/packages/agent/lsp"
12- "github.com/patriceckhart/zot/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"
11+ "github.com/bnema/zut/packages/agent/lsp"
12+ "github.com/bnema/zut/packages/core"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 // EditTool applies one or more exact-match replacements to a file.
M packages/agent/tools/lsp.go +3 -3
@@ -10,9 +10,9 @@ import (
1010 "time"
1111 "unicode/utf8"
1212
13- "github.com/patriceckhart/zot/packages/agent/lsp"
14- "github.com/patriceckhart/zot/packages/core"
15- "github.com/patriceckhart/zot/packages/provider"
13+ "github.com/bnema/zut/packages/agent/lsp"
14+ "github.com/bnema/zut/packages/core"
15+ "github.com/bnema/zut/packages/provider"
1616 )
1717
1818 const maxLSPToolOutput = 60 * 1024
M packages/agent/tools/lsp_diagnostics.go +3 -3
@@ -6,9 +6,9 @@ import (
66 "strings"
77 "time"
88
9- "github.com/patriceckhart/zot/packages/agent/lsp"
10- "github.com/patriceckhart/zot/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/agent/lsp"
10+ "github.com/bnema/zut/packages/core"
11+ "github.com/bnema/zut/packages/provider"
1212 )
1313
1414 const writeDiagnosticsTimeout = 2 * time.Second
M packages/agent/tools/lsp_test.go +2 -2
@@ -10,8 +10,8 @@ import (
1010 "testing"
1111 "unicode/utf8"
1212
13- "github.com/patriceckhart/zot/packages/agent/lsp"
14- "github.com/patriceckhart/zot/packages/provider"
13+ "github.com/bnema/zut/packages/agent/lsp"
14+ "github.com/bnema/zut/packages/provider"
1515 )
1616
1717 func TestLSPToolSchemaAndFormatting(t *testing.T) {
M packages/agent/tools/permissions.go +1 -1
@@ -6,7 +6,7 @@ import (
66 "strings"
77 )
88
9-// PermissionSet is the local-runtime permission contract for a packaged .zot agent.
9+// PermissionSet is the local-runtime permission contract for a packaged .zut agent.
1010 type PermissionSet struct {
1111 FS struct {
1212 Read []string `json:"read"`
M packages/agent/tools/read.go +4 -4
@@ -1,4 +1,4 @@
1-// Package tools implements zot's built-in tools: read, write, edit, bash, and lsp.
1+// Package tools implements zut's built-in tools: read, write, edit, bash, and lsp.
22 package tools
33
44 import (
@@ -11,8 +11,8 @@ import (
1111 "path/filepath"
1212 "strings"
1313
14- "github.com/patriceckhart/zot/packages/core"
15- "github.com/patriceckhart/zot/packages/provider"
14+ "github.com/bnema/zut/packages/core"
15+ "github.com/bnema/zut/packages/provider"
1616 )
1717
1818 const (
@@ -195,7 +195,7 @@ func imageMIME(path string) string {
195195 // returns the real media type, independent of the file's extension.
196196 // Providers validate the declared media type against the actual bytes
197197 // and 400 the whole request on a mismatch, so the extension can never
198-// be trusted. Returns "" when the format is not one zot ships images
198+// be trusted. Returns "" when the format is not one zut ships images
199199 // for, leaving the caller's extension-based guess in place.
200200 func sniffImageMIME(data []byte) string {
201201 switch {
M packages/agent/tools/subagent_spawn.go +3 -3
@@ -7,9 +7,9 @@ import (
77 "strings"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/agent/subagents"
11- "github.com/patriceckhart/zot/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/agent/subagents"
11+ "github.com/bnema/zut/packages/core"
12+ "github.com/bnema/zut/packages/provider"
1313 )
1414
1515 // SubagentSpawnTool lets the main agent fork a background sub-agent
M packages/agent/tools/subagent_spawn_test.go +2 -2
@@ -8,8 +8,8 @@ import (
88 "testing"
99 "time"
1010
11- "github.com/patriceckhart/zot/packages/agent/subagents"
12- "github.com/patriceckhart/zot/packages/provider"
11+ "github.com/bnema/zut/packages/agent/subagents"
12+ "github.com/bnema/zut/packages/provider"
1313 )
1414
1515 type noopSupervisorRunner struct{}
M packages/agent/tools/subagent_status.go +3 -3
@@ -8,9 +8,9 @@ import (
88 "time"
99 "unicode/utf8"
1010
11- "github.com/patriceckhart/zot/packages/agent/subagents"
12- "github.com/patriceckhart/zot/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"
11+ "github.com/bnema/zut/packages/agent/subagents"
12+ "github.com/bnema/zut/packages/core"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 // SubagentStatusTool reports live state for background sub-agents without
M packages/agent/tools/subagent_status_test.go +1 -1
@@ -9,7 +9,7 @@ import (
99 "testing"
1010 "time"
1111
12- "github.com/patriceckhart/zot/packages/agent/subagents"
12+ "github.com/bnema/zut/packages/agent/subagents"
1313 )
1414
1515 type statusTestRunner struct {
M packages/agent/tools/telegram_send.go +2 -2
@@ -6,8 +6,8 @@ import (
66 "fmt"
77 "os"
88
9- "github.com/patriceckhart/zot/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/core"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 // TelegramSender is the small affordance the telegram-send tools call
M packages/agent/tools/tools_test.go +1 -1
@@ -10,7 +10,7 @@ import (
1010 "strings"
1111 "testing"
1212
13- "github.com/patriceckhart/zot/packages/provider"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 func mustJSON(t *testing.T, v any) json.RawMessage {
M packages/agent/tools/write.go +3 -3
@@ -8,9 +8,9 @@ import (
88 "path/filepath"
99 "strings"
1010
11- "github.com/patriceckhart/zot/packages/agent/lsp"
12- "github.com/patriceckhart/zot/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"
11+ "github.com/bnema/zut/packages/agent/lsp"
12+ "github.com/bnema/zut/packages/core"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 // WriteTool writes content to a file, creating parent directories.
M packages/agent/update.go +8 -8
@@ -14,7 +14,7 @@ import (
1414
1515 // updateCheckTTL is how often we hit the GitHub API to look for a new
1616 // release. Half a day is frequent enough to notice the same day a
17-// release ships without spamming the API on every zot launch.
17+// release ships without spamming the API on every zut launch.
1818 const updateCheckTTL = 12 * time.Hour
1919
2020 // updateCheckFile is the on-disk cache, keyed to the current binary
@@ -24,7 +24,7 @@ const updateCheckFile = "update-check.json"
2424
2525 // githubReleasesAPI is the REST endpoint we query. Using the API (not
2626 // the HTML redirect) because the JSON response is stable and small.
27-const githubReleasesAPI = "https://api.github.com/repos/patriceckhart/zot/releases/latest"
27+const githubReleasesAPI = "https://api.github.com/repos/bnema/zut/releases/latest"
2828
2929 const devVersionNotice = "dev version detected; skipping update check"
3030
@@ -46,7 +46,7 @@ func isDevVersion(version string) bool {
4646 version == "0.0.0-dev" || strings.HasPrefix(version, "0.0.0-dev+")
4747 }
4848
49-// updateCache is the on-disk structure written to $ZOT_HOME.
49+// updateCache is the on-disk structure written to $ZUT_HOME.
5050 type updateCache struct {
5151 CheckedAt time.Time `json:"checked_at"`
5252 // The version that was current when we last checked. Invalidates
@@ -63,14 +63,14 @@ type updateCache struct {
6363 // Always returns a usable UpdateInfo (zero-value on error). The
6464 // banner renderer skips the display when Available is false, so a
6565 // network failure silently no-ops; we never block startup on this.
66-func CheckForUpdate(ctx context.Context, zotHome, currentVersion string) UpdateInfo {
66+func CheckForUpdate(ctx context.Context, zutHome, currentVersion string) UpdateInfo {
6767 // Development builds never have an update to offer. Skip without
6868 // reading the cache or touching the network.
6969 if isDevVersion(currentVersion) {
7070 return UpdateInfo{}
7171 }
7272
73- cachePath := filepath.Join(zotHome, updateCheckFile)
73+ cachePath := filepath.Join(zutHome, updateCheckFile)
7474 if c, ok := readUpdateCache(cachePath); ok {
7575 // Cache is fresh and tracks the same binary version.
7676 // Additional guard: only trust the cache when it already
@@ -111,13 +111,13 @@ func CheckForUpdate(ctx context.Context, zotHome, currentVersion string) UpdateI
111111 // CheckForUpdateAsync runs CheckForUpdate in a goroutine, delivers the
112112 // result to the returned channel, and never blocks startup. The
113113 // channel is always closed; receivers should `ok`-check.
114-func CheckForUpdateAsync(zotHome, currentVersion string) <-chan UpdateInfo {
114+func CheckForUpdateAsync(zutHome, currentVersion string) <-chan UpdateInfo {
115115 ch := make(chan UpdateInfo, 1)
116116 go func() {
117117 defer close(ch)
118118 ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
119119 defer cancel()
120- ch <- CheckForUpdate(ctx, zotHome, currentVersion)
120+ ch <- CheckForUpdate(ctx, zutHome, currentVersion)
121121 }()
122122 return ch
123123 }
@@ -133,7 +133,7 @@ func buildInfo(current, latest, url string) UpdateInfo {
133133 }
134134
135135 // versionLess returns a < b for dotted semver-ish tags like "0.0.4".
136-// Non-numeric components compare as zero, which is fine for zot's
136+// Non-numeric components compare as zero, which is fine for zut's
137137 // x.y.z-only scheme.
138138 func versionLess(a, b string) bool {
139139 as := splitVersion(a)
M packages/agent/updatecmd.go +36 -36
@@ -16,7 +16,7 @@ import (
1616 "time"
1717 )
1818
19-// runUpdateCommand dispatches `zot update`. Returns (handled=true, err)
19+// runUpdateCommand dispatches `zut update`. Returns (handled=true, err)
2020 // if rawArgs starts with "update"; otherwise (handled=false, nil) so
2121 // the main router falls through to the regular flag parser. Mirrors
2222 // the shape of runBotCommand / runExtCommand on purpose: identical
@@ -31,7 +31,7 @@ import (
3131 // name template defined in .goreleaser.yaml.
3232 // 3. Downloads checksums.txt and the asset to a temp directory.
3333 // 4. Verifies the asset's sha256 against checksums.txt.
34-// 5. Extracts the zot binary from the archive.
34+// 5. Extracts the zut binary from the archive.
3535 // 6. Atomically replaces the running binary with the new one.
3636 //
3737 // Refuses to operate on development builds because there is no meaningful
@@ -58,25 +58,25 @@ func runUpdateCommand(rawArgs []string, version string) (handled bool, err error
5858 }
5959
6060 func printUpdateHelp() {
61- fmt.Fprintln(os.Stderr, `zot update — replace the current zot binary with the latest release
61+ fmt.Fprintln(os.Stderr, `zut update — replace the current zut binary with the latest release
6262
6363 usage:
64- zot update download and install the newest release
65- zot update --check show what update is available; install nothing
66- zot update --help show this help
64+ zut update download and install the newest release
65+ zut update --check show what update is available; install nothing
66+ zut update --help show this help
6767
6868 notes:
6969 * The binary must be writable by the current user. On a system-wide
70- install (e.g. /usr/local/bin/zot owned by root) re-run with sudo.
70+ install (e.g. /usr/local/bin/zut owned by root) re-run with sudo.
7171 * Development builds are refused. They typically come from a local
7272 source build and shouldn't be silently replaced with a release binary.
7373 * Honours $GITHUB_TOKEN if set, so private-repo releases work.
7474 * After the binary is installed, every extension under
75- $ZOT_HOME/extensions/ that is a git checkout is fast-forward
75+ $ZUT_HOME/extensions/ that is a git checkout is fast-forward
7676 pulled (no merge, no rebase). Dirty worktrees are stashed and
7777 restored. Extensions without a .git directory, disabled
7878 extensions, and pulls that fail (offline, diverged, etc.) are
79- skipped per-extension and never abort the overall update. zot
79+ skipped per-extension and never abort the overall update. zut
8080 does NOT run any build step after pulling — authors are expected
8181 to commit a working binary, or you can rebuild manually and
8282 /reload-ext.`)
@@ -87,7 +87,7 @@ notes:
8787 // pipe into scripts.
8888 func runUpdateCheck(version string) error {
8989 if isDevVersion(version) {
90- fmt.Println("zot:", devVersionNotice)
90+ fmt.Println("zut:", devVersionNotice)
9191 return nil
9292 }
9393 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
@@ -99,24 +99,24 @@ func runUpdateCheck(version string) error {
9999 latest := strings.TrimPrefix(tag, "v")
100100 current := versionOnly(version)
101101 if !versionLess(current, latest) {
102- fmt.Printf("zot %s is up to date (latest: %s)\n", current, latest)
102+ fmt.Printf("zut %s is up to date (latest: %s)\n", current, latest)
103103 return nil
104104 }
105- fmt.Printf("zot %s -> %s available\n release: %s\n run 'zot update' to install\n", current, latest, url)
105+ fmt.Printf("zut %s -> %s available\n release: %s\n run 'zut update' to install\n", current, latest, url)
106106 return nil
107107 }
108108
109-// runUpdate is the meat of `zot update`.
109+// runUpdate is the meat of `zut update`.
110110 func runUpdate(version string) error {
111111 if isDevVersion(version) {
112- return errors.New(devVersionNotice + "; `zot update` is disabled. Build a release tag or download from https://github.com/patriceckhart/zot/releases")
112+ return errors.New(devVersionNotice + "; `zut update` is disabled. Build a release tag or download from https://github.com/bnema/zut/releases")
113113 }
114114 current := versionOnly(version)
115115
116116 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
117117 defer cancel()
118118
119- fmt.Println("zot update: querying latest release...")
119+ fmt.Println("zut update: querying latest release...")
120120 tag, releaseURL, err := fetchLatestRelease(ctx)
121121 if err != nil {
122122 return fmt.Errorf("query latest release: %w", err)
@@ -124,18 +124,18 @@ func runUpdate(version string) error {
124124 latest := strings.TrimPrefix(tag, "v")
125125
126126 if !versionLess(current, latest) {
127- fmt.Printf("zot %s is already up to date.\n", current)
127+ fmt.Printf("zut %s is already up to date.\n", current)
128128 return nil
129129 }
130- fmt.Printf("zot update: %s -> %s\n", current, latest)
131- fmt.Printf("zot update: release page %s\n", releaseURL)
130+ fmt.Printf("zut update: %s -> %s\n", current, latest)
131+ fmt.Printf("zut update: release page %s\n", releaseURL)
132132
133133 // Pick the archive matching this platform.
134134 assetName, archiveFmt, err := releaseAssetName(latest)
135135 if err != nil {
136136 return err
137137 }
138- fmt.Printf("zot update: target asset %s\n", assetName)
138+ fmt.Printf("zut update: target asset %s\n", assetName)
139139
140140 // We assume the standard GoReleaser layout: assets live under
141141 // https://github.com/<owner>/<repo>/releases/download/<tag>/<file>
@@ -146,7 +146,7 @@ func runUpdate(version string) error {
146146 assetURL := base + "/" + assetName
147147 sumsURL := base + "/checksums.txt"
148148
149- tmp, err := os.MkdirTemp("", "zot-update-")
149+ tmp, err := os.MkdirTemp("", "zut-update-")
150150 if err != nil {
151151 return fmt.Errorf("create temp dir: %w", err)
152152 }
@@ -155,7 +155,7 @@ func runUpdate(version string) error {
155155 // users can clear /tmp themselves.
156156 defer func() { _ = os.RemoveAll(tmp) }()
157157
158- fmt.Println("zot update: downloading checksums.txt...")
158+ fmt.Println("zut update: downloading checksums.txt...")
159159 sumsPath := filepath.Join(tmp, "checksums.txt")
160160 if err := downloadFile(ctx, sumsURL, sumsPath); err != nil {
161161 return fmt.Errorf("download checksums: %w", err)
@@ -165,13 +165,13 @@ func runUpdate(version string) error {
165165 return err
166166 }
167167
168- fmt.Println("zot update: downloading archive...")
168+ fmt.Println("zut update: downloading archive...")
169169 archivePath := filepath.Join(tmp, assetName)
170170 if err := downloadFile(ctx, assetURL, archivePath); err != nil {
171171 return fmt.Errorf("download archive: %w", err)
172172 }
173173
174- fmt.Println("zot update: verifying checksum...")
174+ fmt.Println("zut update: verifying checksum...")
175175 gotSum, err := sha256File(archivePath)
176176 if err != nil {
177177 return fmt.Errorf("hash archive: %w", err)
@@ -180,7 +180,7 @@ func runUpdate(version string) error {
180180 return fmt.Errorf("checksum mismatch for %s: got %s, want %s", assetName, gotSum, wantSum)
181181 }
182182
183- fmt.Println("zot update: extracting...")
183+ fmt.Println("zut update: extracting...")
184184 extractDir := filepath.Join(tmp, "extracted")
185185 if err := os.MkdirAll(extractDir, 0o755); err != nil {
186186 return fmt.Errorf("mkdir extract: %w", err)
@@ -189,35 +189,35 @@ func runUpdate(version string) error {
189189 return fmt.Errorf("extract archive: %w", err)
190190 }
191191
192- newBin := filepath.Join(extractDir, "zot")
192+ newBin := filepath.Join(extractDir, "zut")
193193 if runtime.GOOS == "windows" {
194- newBin = filepath.Join(extractDir, "zot.exe")
194+ newBin = filepath.Join(extractDir, "zut.exe")
195195 }
196196 if st, err := os.Stat(newBin); err != nil || st.IsDir() {
197- return fmt.Errorf("extracted archive does not contain a zot binary at %s", newBin)
197+ return fmt.Errorf("extracted archive does not contain a zut binary at %s", newBin)
198198 }
199199
200200 curBin, err := os.Executable()
201201 if err != nil {
202202 return fmt.Errorf("resolve current binary path: %w", err)
203203 }
204- // Resolve symlinks so 'zot' on $PATH that points at /usr/local/bin
204+ // Resolve symlinks so 'zut' on $PATH that points at /usr/local/bin
205205 // gets actually replaced rather than us writing into the symlink
206206 // target's directory while leaving the link stale.
207207 if resolved, err := filepath.EvalSymlinks(curBin); err == nil {
208208 curBin = resolved
209209 }
210210
211- fmt.Printf("zot update: replacing %s\n", curBin)
211+ fmt.Printf("zut update: replacing %s\n", curBin)
212212 if err := replaceBinary(curBin, newBin); err != nil {
213213 return fmt.Errorf("replace binary: %w", err)
214214 }
215- fmt.Printf("zot update: installed %s\n", latest)
215+ fmt.Printf("zut update: installed %s\n", latest)
216216
217217 // Best-effort: also refresh installed extensions that live in
218218 // git checkouts. Failures here are advisory and never abort the
219219 // overall update — the binary swap already succeeded.
220- updateAllExtensions(ZotHome())
220+ updateAllExtensions(ZutHome())
221221
222222 return nil
223223 }
@@ -236,13 +236,13 @@ func releaseAssetName(version string) (name, format string, err error) {
236236 case "windows":
237237 // supported
238238 default:
239- return "", "", fmt.Errorf("unsupported OS for zot update: %s (download manually from the release page)", goos)
239+ return "", "", fmt.Errorf("unsupported OS for zut update: %s (download manually from the release page)", goos)
240240 }
241241 switch goarch {
242242 case "amd64", "arm64":
243243 // supported
244244 default:
245- return "", "", fmt.Errorf("unsupported CPU arch for zot update: %s", goarch)
245+ return "", "", fmt.Errorf("unsupported CPU arch for zut update: %s", goarch)
246246 }
247247 if goos == "windows" && goarch == "arm64" {
248248 return "", "", errors.New("windows/arm64 release artifacts are not published; download manually")
@@ -251,7 +251,7 @@ func releaseAssetName(version string) (name, format string, err error) {
251251 if goos == "windows" {
252252 ext = "zip"
253253 }
254- return fmt.Sprintf("zot_%s_%s_%s.%s", version, goos, goarch, ext), ext, nil
254+ return fmt.Sprintf("zut_%s_%s_%s.%s", version, goos, goarch, ext), ext, nil
255255 }
256256
257257 // downloadFile fetches url to dst, streaming through io.Copy so big
@@ -325,7 +325,7 @@ func sha256File(path string) (string, error) {
325325 }
326326
327327 // extractArchive shells out to the system tar / unzip rather than
328-// pulling in a Go archive lib. Reasoning: zot already depends on
328+// pulling in a Go archive lib. Reasoning: zut already depends on
329329 // system tar implicitly in a few places, every supported platform
330330 // ships tar (BSD tar on macOS handles gzip natively, GNU tar on
331331 // Linux, bsdtar on Windows 10+), and the dependency-free release
@@ -385,7 +385,7 @@ func replaceBinary(cur, newBin string) error {
385385 return fmt.Errorf("install new binary: %w", err)
386386 }
387387 // The .old file is locked until this process exits; leave
388- // it behind. Next `zot update` cleans it up via the
388+ // it behind. Next `zut update` cleans it up via the
389389 // os.Remove(bak) above.
390390 return nil
391391 }
R packages/agent/zotfile.go → packages/agent/zutfile.go +120 -120 renamed, 79% similar
@@ -20,22 +20,22 @@ import (
2020 "strings"
2121 "time"
2222
23+ "github.com/bnema/zut/packages/agent/tools"
24+ "github.com/bnema/zut/packages/provider"
25+ "github.com/bnema/zut/packages/provider/auth"
26+ "github.com/bnema/zut/packages/tui"
2327 "github.com/klauspost/compress/zstd"
24- "github.com/patriceckhart/zot/packages/agent/tools"
25- "github.com/patriceckhart/zot/packages/provider"
26- "github.com/patriceckhart/zot/packages/provider/auth"
27- "github.com/patriceckhart/zot/packages/tui"
2828 "golang.org/x/term"
2929 )
3030
31-type ZotfileManifest struct {
32- Zotfile int `json:"zotfile"`
31+type ZutfileManifest struct {
32+ Zutfile int `json:"zutfile"`
3333 Name string `json:"name"`
3434 Version string `json:"version"`
3535 Description string `json:"description"`
3636 License string `json:"license"`
3737 Runtime struct {
38- MinZot string `json:"min_zot"`
38+ MinZut string `json:"min_zut"`
3939 } `json:"runtime"`
4040 Model struct {
4141 Requires []string `json:"requires"`
@@ -56,14 +56,14 @@ type ZotfileManifest struct {
5656 ReplaceSystemPrompt bool `json:"replace_system_prompt"`
5757 }
5858
59-type zotfileLoaded struct {
59+type zutfileLoaded struct {
6060 Dir string
6161 Temp bool
6262 Digest string
63- Manifest ZotfileManifest
63+ Manifest ZutfileManifest
6464 }
6565
66-func runZotfileCommand(rawArgs []string, version string) (bool, error) {
66+func runZutfileCommand(rawArgs []string, version string) (bool, error) {
6767 if len(rawArgs) == 0 {
6868 return false, nil
6969 }
@@ -77,17 +77,17 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) {
7777 if len(rawArgs) > 2 {
7878 out = rawArgs[2]
7979 }
80- return true, zotPack(dir, out)
80+ return true, zutPack(dir, out)
8181 case "inspect":
8282 if len(rawArgs) < 2 {
83- return true, fmt.Errorf("zot inspect requires a name, .zot file, directory, or GitHub URL")
83+ return true, fmt.Errorf("zut inspect requires a name, .zut file, directory, or GitHub URL")
8484 }
85- return true, zotInspect(rawArgs[1])
85+ return true, zutInspect(rawArgs[1])
8686 case "verify":
8787 if len(rawArgs) < 2 {
88- return true, fmt.Errorf("zot verify requires a name, .zot file, directory, or GitHub URL")
88+ return true, fmt.Errorf("zut verify requires a name, .zut file, directory, or GitHub URL")
8989 }
90- zf, cleanup, err := loadZotfile(rawArgs[1])
90+ zf, cleanup, err := loadZutfile(rawArgs[1])
9191 if cleanup != nil {
9292 defer cleanup()
9393 }
@@ -98,11 +98,11 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) {
9898 return true, nil
9999 case "run":
100100 if len(rawArgs) < 2 {
101- return true, fmt.Errorf("zot run requires a name, .zot file, directory, or GitHub URL")
101+ return true, fmt.Errorf("zut run requires a name, .zut file, directory, or GitHub URL")
102102 }
103103 yes, remaining := peelLeadingYes(rawArgs[1:])
104104 if len(remaining) < 1 {
105- return true, fmt.Errorf("zot run requires a name, .zot file, directory, or GitHub URL")
105+ return true, fmt.Errorf("zut run requires a name, .zut file, directory, or GitHub URL")
106106 }
107107 ref := remaining[0]
108108 rest := remaining[1:]
@@ -114,37 +114,37 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) {
114114 if yes {
115115 args.Yes = true
116116 }
117- return true, runLocalZotfile(ref, args, version)
117+ return true, runLocalZutfile(ref, args, version)
118118 default:
119119 return false, nil
120120 }
121121 }
122122
123-func runLocalZotfile(ref string, args Args, version string) error {
123+func runLocalZutfile(ref string, args Args, version string) error {
124124 prepareRuntimeCatalog()
125- zf, cleanup, err := loadZotfile(ref)
125+ zf, cleanup, err := loadZutfile(ref)
126126 if cleanup != nil {
127127 defer cleanup()
128128 }
129129 if err != nil {
130130 return err
131131 }
132- if err := checkZotfileRequirements(zf, version); err != nil {
132+ if err := checkZutfileRequirements(zf, version); err != nil {
133133 return err
134134 }
135- agentData := filepath.Join(ZotHome(), "agents", safeAgentName(zf.Manifest.Name), "data")
135+ agentData := filepath.Join(ZutHome(), "agents", safeAgentName(zf.Manifest.Name), "data")
136136 if err := os.MkdirAll(agentData, 0o755); err != nil {
137137 return err
138138 }
139139 perms := zf.Manifest.Permissions.Expand(args.CWD, agentData)
140- allowed, err := consentZotfile(zf, perms, args.Yes)
140+ allowed, err := consentZutfile(zf, perms, args.Yes)
141141 if err != nil {
142142 return err
143143 }
144144 if !allowed {
145145 return nil
146146 }
147- if err := applyZotfileModelRequirements(&args, zf.Manifest); err != nil {
147+ if err := applyZutfileModelRequirements(&args, zf.Manifest); err != nil {
148148 return err
149149 }
150150 agentPath := filepath.Join(zf.Dir, "AGENT.md")
@@ -167,13 +167,13 @@ func runLocalZotfile(ref string, args Args, version string) error {
167167 args.Prompt = *zf.Manifest.Entry.DefaultPrompt
168168 }
169169 if dirExists(filepath.Join(zf.Dir, "skills")) {
170- old := os.Getenv("ZOT_AGENT_SKILLS")
170+ old := os.Getenv("ZUT_AGENT_SKILLS")
171171 v := filepath.Join(zf.Dir, "skills")
172172 if old != "" {
173173 v += string(os.PathListSeparator) + old
174174 }
175- _ = os.Setenv("ZOT_AGENT_SKILLS", v)
176- defer os.Setenv("ZOT_AGENT_SKILLS", old)
175+ _ = os.Setenv("ZUT_AGENT_SKILLS", v)
176+ defer os.Setenv("ZUT_AGENT_SKILLS", old)
177177 }
178178 args.AgentName = zf.Manifest.Name
179179 args.AgentDataDir = agentData
@@ -181,8 +181,8 @@ func runLocalZotfile(ref string, args Args, version string) error {
181181 return runWithArgs(args, version)
182182 }
183183
184-func zotInspect(ref string) error {
185- zf, cleanup, err := loadZotfile(ref)
184+func zutInspect(ref string) error {
185+ zf, cleanup, err := loadZutfile(ref)
186186 if cleanup != nil {
187187 defer cleanup()
188188 }
@@ -207,7 +207,7 @@ func zotInspect(ref string) error {
207207 })
208208 }
209209
210-func applyZotfileModelRequirements(args *Args, m ZotfileManifest) error {
210+func applyZutfileModelRequirements(args *Args, m ZutfileManifest) error {
211211 minCtx := m.Model.MinContext
212212 requires := map[string]bool{}
213213 for _, capability := range m.Model.Requires {
@@ -219,7 +219,7 @@ func applyZotfileModelRequirements(args *Args, m ZotfileManifest) error {
219219 }
220220 }
221221 if m.Model.MinTier != "" {
222- return fmt.Errorf("model.min_tier is not supported by this zot version")
222+ return fmt.Errorf("model.min_tier is not supported by this zut version")
223223 }
224224 compatible := func(model provider.Model) bool {
225225 if model.ContextWindow < minCtx {
@@ -289,27 +289,27 @@ func prepareRuntimeCatalog() {
289289 RefreshModelsAsync()
290290 }
291291
292-func zotPack(dir, out string) error {
292+func zutPack(dir, out string) error {
293293 abs, err := filepath.Abs(dir)
294294 if err != nil {
295295 return err
296296 }
297- if _, err := readZotManifest(abs); err != nil {
297+ if _, err := readZutManifest(abs); err != nil {
298298 return err
299299 }
300- if err := validateZotfileDir(abs); err != nil {
300+ if err := validateZutfileDir(abs); err != nil {
301301 return err
302302 }
303303 if out == "" {
304- m, _ := readZotManifest(abs)
304+ m, _ := readZutManifest(abs)
305305 base := safeAgentName(m.Name)
306306 if base == "" {
307307 base = filepath.Base(abs)
308308 }
309- out = base + ".zot"
309+ out = base + ".zut"
310310 }
311- if filepath.Ext(out) != ".zot" {
312- out += ".zot"
311+ if filepath.Ext(out) != ".zut" {
312+ out += ".zut"
313313 }
314314 outAbs, err := filepath.Abs(out)
315315 if err != nil {
@@ -337,53 +337,53 @@ func zotPack(dir, out string) error {
337337 return nil
338338 }
339339
340-func loadZotfile(ref string) (zotfileLoaded, func(), error) {
341- resolved, err := resolveZotfileRef(ref)
340+func loadZutfile(ref string) (zutfileLoaded, func(), error) {
341+ resolved, err := resolveZutfileRef(ref)
342342 if err != nil {
343- return zotfileLoaded{}, nil, err
343+ return zutfileLoaded{}, nil, err
344344 }
345345 ref = resolved
346346 if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
347- return loadRemoteZotfile(u)
347+ return loadRemoteZutfile(u)
348348 }
349349 info, err := os.Stat(ref)
350350 if err != nil {
351- return zotfileLoaded{}, nil, err
351+ return zutfileLoaded{}, nil, err
352352 }
353353 if info.IsDir() {
354354 abs, _ := filepath.Abs(ref)
355- m, err := readZotManifest(abs)
355+ m, err := readZutManifest(abs)
356356 if err != nil {
357- return zotfileLoaded{}, nil, err
357+ return zutfileLoaded{}, nil, err
358358 }
359- if err := validateZotfileDir(abs); err != nil {
360- return zotfileLoaded{}, nil, err
359+ if err := validateZutfileDir(abs); err != nil {
360+ return zutfileLoaded{}, nil, err
361361 }
362- return zotfileLoaded{Dir: abs, Manifest: m, Digest: digestDirectory(abs)}, nil, nil
362+ return zutfileLoaded{Dir: abs, Manifest: m, Digest: digestDirectory(abs)}, nil, nil
363363 }
364- tmp, err := os.MkdirTemp("", "zotfile-*")
364+ tmp, err := os.MkdirTemp("", "zutfile-*")
365365 if err != nil {
366- return zotfileLoaded{}, nil, err
366+ return zutfileLoaded{}, nil, err
367367 }
368368 cleanup := func() { _ = os.RemoveAll(tmp) }
369- digest, err := unpackZotArchive(ref, tmp)
369+ digest, err := unpackZutArchive(ref, tmp)
370370 if err != nil {
371371 cleanup()
372- return zotfileLoaded{}, nil, err
372+ return zutfileLoaded{}, nil, err
373373 }
374- m, err := readZotManifest(tmp)
374+ m, err := readZutManifest(tmp)
375375 if err != nil {
376376 cleanup()
377- return zotfileLoaded{}, nil, err
377+ return zutfileLoaded{}, nil, err
378378 }
379- if err := validateZotfileDir(tmp); err != nil {
379+ if err := validateZutfileDir(tmp); err != nil {
380380 cleanup()
381- return zotfileLoaded{}, nil, err
381+ return zutfileLoaded{}, nil, err
382382 }
383- return zotfileLoaded{Dir: tmp, Temp: true, Digest: digest, Manifest: m}, cleanup, nil
383+ return zutfileLoaded{Dir: tmp, Temp: true, Digest: digest, Manifest: m}, cleanup, nil
384384 }
385385
386-func resolveZotfileRef(ref string) (string, error) {
386+func resolveZutfileRef(ref string) (string, error) {
387387 if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
388388 return ref, nil
389389 }
@@ -393,24 +393,24 @@ func resolveZotfileRef(ref string) (string, error) {
393393 return "", err
394394 }
395395 if filepath.Ext(ref) == "" {
396- archive := ref + ".zot"
396+ archive := ref + ".zut"
397397 if _, err := os.Stat(archive); err == nil {
398398 return archive, nil
399399 } else if !os.IsNotExist(err) {
400400 return "", err
401401 }
402402 }
403- if strings.HasSuffix(strings.ToLower(ref), ".zot") {
403+ if strings.HasSuffix(strings.ToLower(ref), ".zut") {
404404 return ref, nil
405405 }
406406 githubRef := strings.ReplaceAll(ref, "\\", "/")
407407 parts := strings.Split(githubRef, "/")
408- if len(parts) == 2 && validGitHubZotfileSegment(parts[0]) && validGitHubZotfileSegment(parts[1]) {
408+ if len(parts) == 2 && validGitHubZutfileSegment(parts[0]) && validGitHubZutfileSegment(parts[1]) {
409409 return "https://github.com/" + parts[0] + "/" + parts[1], nil
410410 }
411411 if len(parts) >= 3 {
412412 for _, part := range parts {
413- if !validGitHubZotfileSegment(part) {
413+ if !validGitHubZutfileSegment(part) {
414414 return ref, nil
415415 }
416416 }
@@ -419,37 +419,37 @@ func resolveZotfileRef(ref string) (string, error) {
419419 return ref, nil
420420 }
421421
422-func validGitHubZotfileSegment(s string) bool {
422+func validGitHubZutfileSegment(s string) bool {
423423 return s != "" && s == strings.ToLower(s) && safeAgentName(s) == s
424424 }
425425
426-var zotfileHTTPClient = &http.Client{Timeout: 60 * time.Second}
426+var zutfileHTTPClient = &http.Client{Timeout: 60 * time.Second}
427427
428428 var githubArchiveURL = func(owner, repo, ref string) string {
429429 return fmt.Sprintf("https://github.com/%s/%s/archive/%s.tar.gz", owner, repo, url.PathEscape(ref))
430430 }
431431
432-func loadRemoteZotfile(u *url.URL) (zotfileLoaded, func(), error) {
432+func loadRemoteZutfile(u *url.URL) (zutfileLoaded, func(), error) {
433433 if !strings.EqualFold(u.Hostname(), "github.com") {
434- return zotfileLoaded{}, nil, fmt.Errorf("unsupported zotfile URL host %q; only github.com agent directories are supported", u.Hostname())
434+ return zutfileLoaded{}, nil, fmt.Errorf("unsupported zutfile URL host %q; only github.com agent directories are supported", u.Hostname())
435435 }
436436 owner, repo, ref, subdir, err := parseGitHubAgentURL(u)
437437 if err != nil {
438- return zotfileLoaded{}, nil, err
438+ return zutfileLoaded{}, nil, err
439439 }
440- tmp, err := os.MkdirTemp("", "zotfile-github-*")
440+ tmp, err := os.MkdirTemp("", "zutfile-github-*")
441441 if err != nil {
442- return zotfileLoaded{}, nil, err
442+ return zutfileLoaded{}, nil, err
443443 }
444444 cleanup := func() { _ = os.RemoveAll(tmp) }
445445 if err := downloadGitHubArchive(githubArchiveURL(owner, repo, ref), tmp); err != nil {
446446 cleanup()
447- return zotfileLoaded{}, nil, err
447+ return zutfileLoaded{}, nil, err
448448 }
449449 root, err := singleExtractedRoot(tmp)
450450 if err != nil {
451451 cleanup()
452- return zotfileLoaded{}, nil, err
452+ return zutfileLoaded{}, nil, err
453453 }
454454 dir := filepath.Join(root, filepath.FromSlash(subdir))
455455 if subdir == "" {
@@ -457,18 +457,18 @@ func loadRemoteZotfile(u *url.URL) (zotfileLoaded, func(), error) {
457457 }
458458 if !pathWithin(root, dir) {
459459 cleanup()
460- return zotfileLoaded{}, nil, fmt.Errorf("unsafe GitHub agent path %q", subdir)
460+ return zutfileLoaded{}, nil, fmt.Errorf("unsafe GitHub agent path %q", subdir)
461461 }
462- m, err := readZotManifest(dir)
462+ m, err := readZutManifest(dir)
463463 if err != nil {
464464 cleanup()
465- return zotfileLoaded{}, nil, fmt.Errorf("GitHub agent %s/%s/%s: %w", owner, repo, subdir, err)
465+ return zutfileLoaded{}, nil, fmt.Errorf("GitHub agent %s/%s/%s: %w", owner, repo, subdir, err)
466466 }
467- if err := validateZotfileDir(dir); err != nil {
467+ if err := validateZutfileDir(dir); err != nil {
468468 cleanup()
469- return zotfileLoaded{}, nil, err
469+ return zutfileLoaded{}, nil, err
470470 }
471- return zotfileLoaded{Dir: dir, Temp: true, Digest: digestDirectory(dir), Manifest: m}, cleanup, nil
471+ return zutfileLoaded{Dir: dir, Temp: true, Digest: digestDirectory(dir), Manifest: m}, cleanup, nil
472472 }
473473
474474 func parseGitHubAgentURL(u *url.URL) (owner, repo, ref, subdir string, err error) {
@@ -507,8 +507,8 @@ func downloadGitHubArchive(archiveURL, dst string) error {
507507 if err != nil {
508508 return err
509509 }
510- req.Header.Set("User-Agent", "zot")
511- resp, err := zotfileHTTPClient.Do(req)
510+ req.Header.Set("User-Agent", "zut")
511+ resp, err := zutfileHTTPClient.Do(req)
512512 if err != nil {
513513 return fmt.Errorf("download GitHub repository: %w", err)
514514 }
@@ -516,7 +516,7 @@ func downloadGitHubArchive(archiveURL, dst string) error {
516516 if resp.StatusCode != http.StatusOK {
517517 return fmt.Errorf("download GitHub repository: HTTP %d", resp.StatusCode)
518518 }
519- limited := &io.LimitedReader{R: resp.Body, N: maxZotfileCompressedSize + 1}
519+ limited := &io.LimitedReader{R: resp.Body, N: maxZutfileCompressedSize + 1}
520520 gr, err := gzip.NewReader(limited)
521521 if err != nil {
522522 return fmt.Errorf("read GitHub archive: %w", err)
@@ -526,7 +526,7 @@ func downloadGitHubArchive(archiveURL, dst string) error {
526526 return fmt.Errorf("extract GitHub archive: %w", err)
527527 }
528528 if limited.N <= 0 {
529- return fmt.Errorf("GitHub archive exceeds %d MiB compressed size limit", maxZotfileCompressedSize>>20)
529+ return fmt.Errorf("GitHub archive exceeds %d MiB compressed size limit", maxZutfileCompressedSize>>20)
530530 }
531531 return nil
532532 }
@@ -547,8 +547,8 @@ func singleExtractedRoot(dir string) (string, error) {
547547 return filepath.Join(dir, entries[0].Name()), nil
548548 }
549549
550-func readZotManifest(dir string) (ZotfileManifest, error) {
551- var m ZotfileManifest
550+func readZutManifest(dir string) (ZutfileManifest, error) {
551+ var m ZutfileManifest
552552 b, err := os.ReadFile(filepath.Join(dir, "manifest.json"))
553553 if err != nil {
554554 return m, fmt.Errorf("manifest.json is required: %w", err)
@@ -556,8 +556,8 @@ func readZotManifest(dir string) (ZotfileManifest, error) {
556556 if err := json.Unmarshal(b, &m); err != nil {
557557 return m, fmt.Errorf("manifest.json: %w", err)
558558 }
559- if m.Zotfile != 1 {
560- return m, fmt.Errorf("unsupported zotfile version %d", m.Zotfile)
559+ if m.Zutfile != 1 {
560+ return m, fmt.Errorf("unsupported zutfile version %d", m.Zutfile)
561561 }
562562 name := strings.TrimSpace(m.Name)
563563 if name == "" {
@@ -582,7 +582,7 @@ func readZotManifest(dir string) (ZotfileManifest, error) {
582582 return m, nil
583583 }
584584
585-func validateZotfileDir(dir string) error {
585+func validateZutfileDir(dir string) error {
586586 agentPath := filepath.Join(dir, "AGENT.md")
587587 st, err := os.Stat(agentPath)
588588 switch {
@@ -602,25 +602,25 @@ func validateZotfileDir(dir string) error {
602602 }
603603
604604 const (
605- maxZotfileCompressedSize = 100 << 20
606- maxZotfileEntrySize = 64 << 20
607- maxZotfileExpandedSize = 256 << 20
605+ maxZutfileCompressedSize = 100 << 20
606+ maxZutfileEntrySize = 64 << 20
607+ maxZutfileExpandedSize = 256 << 20
608608 )
609609
610-func unpackZotArchive(path, dst string) (string, error) {
610+func unpackZutArchive(path, dst string) (string, error) {
611611 info, err := os.Stat(path)
612612 if err != nil {
613613 return "", err
614614 }
615- if info.Size() > maxZotfileCompressedSize {
616- return "", fmt.Errorf("zotfile exceeds %d MiB compressed size limit", maxZotfileCompressedSize>>20)
615+ if info.Size() > maxZutfileCompressedSize {
616+ return "", fmt.Errorf("zutfile exceeds %d MiB compressed size limit", maxZutfileCompressedSize>>20)
617617 }
618618 b, err := os.ReadFile(path)
619619 if err != nil {
620620 return "", err
621621 }
622622 digest := sha256.Sum256(b)
623- r, err := zstd.NewReader(bytes.NewReader(b), zstd.WithDecoderMaxMemory(maxZotfileExpandedSize))
623+ r, err := zstd.NewReader(bytes.NewReader(b), zstd.WithDecoderMaxMemory(maxZutfileExpandedSize))
624624 if err != nil {
625625 // Development fallback for older experiments.
626626 gr, gerr := gzip.NewReader(bytes.NewReader(b))
@@ -650,7 +650,7 @@ func untar(r io.Reader, dst string) error {
650650 return fmt.Errorf("unsafe path in archive: %s", h.Name)
651651 }
652652 path := filepath.Join(dst, name)
653- if h.Size < 0 || h.Size > maxZotfileEntrySize || expanded+h.Size > maxZotfileExpandedSize {
653+ if h.Size < 0 || h.Size > maxZutfileEntrySize || expanded+h.Size > maxZutfileExpandedSize {
654654 return fmt.Errorf("archive content exceeds extraction size limit: %s", h.Name)
655655 }
656656 expanded += h.Size
@@ -699,7 +699,7 @@ func writeCanonicalTar(root string, w io.Writer, exclude ...string) error {
699699 return nil
700700 }
701701 if d.Type()&os.ModeSymlink != 0 {
702- return fmt.Errorf("symlinks are not supported in zotfiles: %s", path)
702+ return fmt.Errorf("symlinks are not supported in zutfiles: %s", path)
703703 }
704704 files = append(files, path)
705705 return nil
@@ -747,13 +747,13 @@ func writeCanonicalTar(root string, w io.Writer, exclude ...string) error {
747747 return nil
748748 }
749749
750-func checkZotfileRequirements(zf zotfileLoaded, version string) error {
751- if min := strings.TrimSpace(zf.Manifest.Runtime.MinZot); min != "" {
750+func checkZutfileRequirements(zf zutfileLoaded, version string) error {
751+ if min := strings.TrimSpace(zf.Manifest.Runtime.MinZut); min != "" {
752752 if isDevVersion(version) {
753- return fmt.Errorf("agent requires zot %s or newer; unversioned development builds cannot satisfy min_zot", min)
753+ return fmt.Errorf("agent requires zut %s or newer; unversioned development builds cannot satisfy min_zut", min)
754754 }
755755 if versionLess(version, min) {
756- return fmt.Errorf("agent requires zot %s or newer; running %s", min, versionOnly(version))
756+ return fmt.Errorf("agent requires zut %s or newer; running %s", min, versionOnly(version))
757757 }
758758 }
759759 if len(zf.Manifest.Requirements.OS) > 0 {
@@ -794,14 +794,14 @@ func peelLeadingYes(args []string) (yes bool, rest []string) {
794794 return yes, rest
795795 }
796796
797-func consentZotfile(zf zotfileLoaded, perms tools.PermissionSet, autoYes bool) (bool, error) {
798- if autoYes || os.Getenv("ZOT_AGENT_CONSENT") == "1" {
799- return acceptZotfileConsent(zf, perms)
797+func consentZutfile(zf zutfileLoaded, perms tools.PermissionSet, autoYes bool) (bool, error) {
798+ if autoYes || os.Getenv("ZUT_AGENT_CONSENT") == "1" {
799+ return acceptZutfileConsent(zf, perms)
800800 }
801801 // "ask" deliberately requires approval on every launch. Other consent
802802 // is durable only for this exact artifact digest, so any package change
803803 // causes a fresh prompt.
804- consentPath := filepath.Join(ZotHome(), "agents", safeAgentName(zf.Manifest.Name), "consents", zf.Digest+".json")
804+ consentPath := filepath.Join(ZutHome(), "agents", safeAgentName(zf.Manifest.Name), "consents", zf.Digest+".json")
805805 if strings.ToLower(strings.TrimSpace(perms.Bash.Mode)) != "ask" {
806806 if _, err := os.Stat(consentPath); err == nil {
807807 return true, nil
@@ -811,27 +811,27 @@ func consentZotfile(zf zotfileLoaded, perms tools.PermissionSet, autoYes bool) (
811811 th := tui.Dark
812812 if interactive {
813813 cfg, _ := LoadConfig()
814- th, _, _ = tui.DetectThemeWithCustom(ZotHome(), cfg.Theme, 80*time.Millisecond)
814+ th, _, _ = tui.DetectThemeWithCustom(ZutHome(), cfg.Theme, 80*time.Millisecond)
815815 }
816- fmt.Print(formatZotfileConsent(zf, perms, th, interactive))
816+ fmt.Print(formatZutfileConsent(zf, perms, th, interactive))
817817 if !interactive {
818- return false, fmt.Errorf("refusing to run without interactive consent; pass -y/--yes or set ZOT_AGENT_CONSENT=1 to allow")
818+ return false, fmt.Errorf("refusing to run without interactive consent; pass -y/--yes or set ZUT_AGENT_CONSENT=1 to allow")
819819 }
820- allowed, err := readZotfileConsent(os.Stdin, os.Stdout, th)
820+ allowed, err := readZutfileConsent(os.Stdin, os.Stdout, th)
821821 if err != nil || !allowed {
822822 return allowed, err
823823 }
824- return acceptZotfileConsent(zf, perms)
824+ return acceptZutfileConsent(zf, perms)
825825 }
826826
827-// acceptZotfileConsent records durable consent when the bash mode is not
827+// acceptZutfileConsent records durable consent when the bash mode is not
828828 // "ask". Ask mode never caches receipts so every launch re-prompts unless
829-// -y / ZOT_AGENT_CONSENT is used for that run.
830-func acceptZotfileConsent(zf zotfileLoaded, perms tools.PermissionSet) (bool, error) {
829+// -y / ZUT_AGENT_CONSENT is used for that run.
830+func acceptZutfileConsent(zf zutfileLoaded, perms tools.PermissionSet) (bool, error) {
831831 if strings.ToLower(strings.TrimSpace(perms.Bash.Mode)) == "ask" {
832832 return true, nil
833833 }
834- consentPath := filepath.Join(ZotHome(), "agents", safeAgentName(zf.Manifest.Name), "consents", zf.Digest+".json")
834+ consentPath := filepath.Join(ZutHome(), "agents", safeAgentName(zf.Manifest.Name), "consents", zf.Digest+".json")
835835 if err := os.MkdirAll(filepath.Dir(consentPath), 0o700); err != nil {
836836 return false, fmt.Errorf("save agent consent: %w", err)
837837 }
@@ -843,9 +843,9 @@ func acceptZotfileConsent(zf zotfileLoaded, perms tools.PermissionSet) (bool, er
843843 return true, nil
844844 }
845845
846-var errZotfileConsentInterrupted = errors.New("interrupted")
846+var errZutfileConsentInterrupted = errors.New("interrupted")
847847
848-func formatZotfileConsent(zf zotfileLoaded, perms tools.PermissionSet, th tui.Theme, color bool) string {
848+func formatZutfileConsent(zf zutfileLoaded, perms tools.PermissionSet, th tui.Theme, color bool) string {
849849 style := func(c tui.TerminalColor, text string) string {
850850 if !color {
851851 return text
@@ -870,10 +870,10 @@ func formatZotfileConsent(zf zotfileLoaded, perms tools.PermissionSet, th tui.Th
870870 return sb.String()
871871 }
872872
873-// readZotfileConsent accepts one y/n answer followed by Enter without echoing
873+// readZutfileConsent accepts one y/n answer followed by Enter without echoing
874874 // unrelated input. Raw mode also turns Ctrl+C into a byte so the terminal can
875875 // be restored before the command aborts.
876-func readZotfileConsent(in *os.File, out io.Writer, th tui.Theme) (bool, error) {
876+func readZutfileConsent(in *os.File, out io.Writer, th tui.Theme) (bool, error) {
877877 fd := int(in.Fd())
878878 state, err := term.MakeRaw(fd)
879879 if err != nil {
@@ -881,7 +881,7 @@ func readZotfileConsent(in *os.File, out io.Writer, th tui.Theme) (bool, error)
881881 }
882882 defer func() { _ = term.Restore(fd, state) }()
883883
884- answer, err := readZotfileConsentKey(in, func(answer byte) {
884+ answer, err := readZutfileConsentKey(in, func(answer byte) {
885885 if answer == 0 {
886886 fmt.Fprint(out, "\b \b")
887887 return
@@ -889,7 +889,7 @@ func readZotfileConsent(in *os.File, out io.Writer, th tui.Theme) (bool, error)
889889 fmt.Fprint(out, th.FGColor(th.FG, string(answer)))
890890 })
891891 switch {
892- case errors.Is(err, errZotfileConsentInterrupted):
892+ case errors.Is(err, errZutfileConsentInterrupted):
893893 fmt.Fprint(out, "^C\r\n")
894894 return false, err
895895 case err != nil:
@@ -901,7 +901,7 @@ func readZotfileConsent(in *os.File, out io.Writer, th tui.Theme) (bool, error)
901901 }
902902 }
903903
904-func readZotfileConsentKey(r io.Reader, echo func(byte)) (byte, error) {
904+func readZutfileConsentKey(r io.Reader, echo func(byte)) (byte, error) {
905905 var answer byte
906906 var b [1]byte
907907 for {
@@ -928,7 +928,7 @@ func readZotfileConsentKey(r io.Reader, echo func(byte)) (byte, error) {
928928 }
929929 }
930930 case 3: // Ctrl+C in raw mode.
931- return 0, errZotfileConsentInterrupted
931+ return 0, errZutfileConsentInterrupted
932932 }
933933 }
934934 }
R packages/agent/zotfile_pre_test.go → packages/agent/zutfile_pre_test.go +8 -8 renamed, 72% similar
@@ -7,8 +7,8 @@ import (
77 "strings"
88 "testing"
99
10- "github.com/patriceckhart/zot/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/core"
11+ "github.com/bnema/zut/packages/provider"
1212 )
1313
1414 func TestStreamTextSinkDoesNotRepeatCompletedMessage(t *testing.T) {
@@ -26,12 +26,12 @@ func TestStreamTextSinkDoesNotRepeatCompletedMessage(t *testing.T) {
2626 }
2727 }
2828
29-func TestRunZotfileStartupPreShellFailure(t *testing.T) {
29+func TestRunZutfileStartupPreShellFailure(t *testing.T) {
3030 pre := "!false"
3131 if runtime.GOOS == "windows" {
3232 pre = "!exit 1"
3333 }
34- err := runZotfileStartupPre(context.Background(), pre, t.TempDir(), nil, nil, nil, nil)
34+ err := runZutfileStartupPre(context.Background(), pre, t.TempDir(), nil, nil, nil, nil)
3535 if err == nil {
3636 t.Fatal("expected entry.pre shell failure")
3737 }
@@ -40,19 +40,19 @@ func TestRunZotfileStartupPreShellFailure(t *testing.T) {
4040 }
4141 }
4242
43-func TestRunZotfileStartupPreEmpty(t *testing.T) {
44- if err := runZotfileStartupPre(context.Background(), " ", t.TempDir(), nil, nil, nil, nil); err != nil {
43+func TestRunZutfileStartupPreEmpty(t *testing.T) {
44+ if err := runZutfileStartupPre(context.Background(), " ", t.TempDir(), nil, nil, nil, nil); err != nil {
4545 t.Fatalf("empty pre: %v", err)
4646 }
4747 }
4848
49-func TestRunZotfileStartupPreShellStreams(t *testing.T) {
49+func TestRunZutfileStartupPreShellStreams(t *testing.T) {
5050 cmd := "!printf streamed-pre"
5151 if runtime.GOOS == "windows" {
5252 cmd = "!echo streamed-pre"
5353 }
5454 var out strings.Builder
55- if err := runZotfileStartupPre(context.Background(), cmd, t.TempDir(), nil, nil, nil, &out); err != nil {
55+ if err := runZutfileStartupPre(context.Background(), cmd, t.TempDir(), nil, nil, nil, &out); err != nil {
5656 t.Fatal(err)
5757 }
5858 if !strings.Contains(out.String(), "streamed-pre") {
R packages/agent/zotfile_test.go → packages/agent/zutfile_test.go +69 -69 renamed, 71% similar
@@ -14,11 +14,11 @@ import (
1414 "strings"
1515 "testing"
1616
17- "github.com/patriceckhart/zot/packages/agent/tools"
18- "github.com/patriceckhart/zot/packages/tui"
17+ "github.com/bnema/zut/packages/agent/tools"
18+ "github.com/bnema/zut/packages/tui"
1919 )
2020
21-func TestReadZotfileConsentKeyAcceptsOnlyYesNo(t *testing.T) {
21+func TestReadZutfileConsentKeyAcceptsOnlyYesNo(t *testing.T) {
2222 tests := []struct {
2323 name string
2424 input []byte
@@ -37,7 +37,7 @@ func TestReadZotfileConsentKeyAcceptsOnlyYesNo(t *testing.T) {
3737 for _, tt := range tests {
3838 t.Run(tt.name, func(t *testing.T) {
3939 var out bytes.Buffer
40- got, err := readZotfileConsentKey(bytes.NewReader(tt.input), func(answer byte) {
40+ got, err := readZutfileConsentKey(bytes.NewReader(tt.input), func(answer byte) {
4141 if answer == 0 {
4242 out.Truncate(out.Len() - 1)
4343 return
@@ -57,9 +57,9 @@ func TestReadZotfileConsentKeyAcceptsOnlyYesNo(t *testing.T) {
5757 }
5858 }
5959
60-func TestReadZotfileConsentKeyWaitsForEnter(t *testing.T) {
60+func TestReadZutfileConsentKeyWaitsForEnter(t *testing.T) {
6161 var out bytes.Buffer
62- if _, err := readZotfileConsentKey(strings.NewReader("y"), func(answer byte) {
62+ if _, err := readZutfileConsentKey(strings.NewReader("y"), func(answer byte) {
6363 out.WriteByte(answer)
6464 }); !errors.Is(err, io.EOF) {
6565 t.Fatalf("error = %v, want EOF before Enter", err)
@@ -69,19 +69,19 @@ func TestReadZotfileConsentKeyWaitsForEnter(t *testing.T) {
6969 }
7070 }
7171
72-func TestReadZotfileConsentKeyInterruptsOnCtrlC(t *testing.T) {
73- if _, err := readZotfileConsentKey(bytes.NewReader([]byte{'x', 3, 'y'}), nil); !errors.Is(err, errZotfileConsentInterrupted) {
72+func TestReadZutfileConsentKeyInterruptsOnCtrlC(t *testing.T) {
73+ if _, err := readZutfileConsentKey(bytes.NewReader([]byte{'x', 3, 'y'}), nil); !errors.Is(err, errZutfileConsentInterrupted) {
7474 t.Fatalf("error = %v, want interrupted", err)
7575 }
7676 }
7777
78-func TestReadZotfileConsentKeyReportsInputError(t *testing.T) {
79- if _, err := readZotfileConsentKey(strings.NewReader("xxx"), nil); !errors.Is(err, io.EOF) {
78+func TestReadZutfileConsentKeyReportsInputError(t *testing.T) {
79+ if _, err := readZutfileConsentKey(strings.NewReader("xxx"), nil); !errors.Is(err, io.EOF) {
8080 t.Fatalf("error = %v, want EOF", err)
8181 }
8282 }
8383
84-func writeTestZotfile(t *testing.T, manifest string) string {
84+func writeTestZutfile(t *testing.T, manifest string) string {
8585 t.Helper()
8686 dir := t.TempDir()
8787 if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(manifest), 0o600); err != nil {
@@ -93,12 +93,12 @@ func writeTestZotfile(t *testing.T, manifest string) string {
9393 return dir
9494 }
9595
96-func TestLoadZotfileAllowsMissingAgentMD(t *testing.T) {
96+func TestLoadZutfileAllowsMissingAgentMD(t *testing.T) {
9797 dir := t.TempDir()
98- if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"zotfile":1,"name":"test"}`), 0o600); err != nil {
98+ if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"zutfile":1,"name":"test"}`), 0o600); err != nil {
9999 t.Fatal(err)
100100 }
101- zf, cleanup, err := loadZotfile(dir)
101+ zf, cleanup, err := loadZutfile(dir)
102102 if cleanup != nil {
103103 defer cleanup()
104104 }
@@ -110,65 +110,65 @@ func TestLoadZotfileAllowsMissingAgentMD(t *testing.T) {
110110 }
111111 }
112112
113-func TestValidateZotfileDirRejectsAgentMDDirectory(t *testing.T) {
113+func TestValidateZutfileDirRejectsAgentMDDirectory(t *testing.T) {
114114 dir := t.TempDir()
115115 if err := os.Mkdir(filepath.Join(dir, "AGENT.md"), 0o700); err != nil {
116116 t.Fatal(err)
117117 }
118- if err := validateZotfileDir(dir); err == nil || !strings.Contains(err.Error(), "regular file") {
118+ if err := validateZutfileDir(dir); err == nil || !strings.Contains(err.Error(), "regular file") {
119119 t.Fatalf("unexpected error: %v", err)
120120 }
121121 }
122122
123-func TestLoadZotfileRejectsUnenforcedPermissions(t *testing.T) {
123+func TestLoadZutfileRejectsUnenforcedPermissions(t *testing.T) {
124124 for _, field := range []string{
125125 `"net":{"allow":["example.com"]}`,
126126 `"env":{"read":["HOME"]}`,
127127 } {
128- dir := writeTestZotfile(t, `{"zotfile":1,"name":"test","permissions":{`+field+`}}`)
129- if _, _, err := loadZotfile(dir); err == nil {
128+ dir := writeTestZutfile(t, `{"zutfile":1,"name":"test","permissions":{`+field+`}}`)
129+ if _, _, err := loadZutfile(dir); err == nil {
130130 t.Fatalf("manifest with %s was accepted", field)
131131 }
132132 }
133133 }
134134
135-func TestLoadZotfileBashModes(t *testing.T) {
135+func TestLoadZutfileBashModes(t *testing.T) {
136136 for _, mode := range []string{"none", "ask", "allowlist"} {
137- manifest := `{"zotfile":1,"name":"test","permissions":{"bash":{"mode":"` + mode + `"}}}`
137+ manifest := `{"zutfile":1,"name":"test","permissions":{"bash":{"mode":"` + mode + `"}}}`
138138 if mode == "allowlist" {
139- manifest = `{"zotfile":1,"name":"test","permissions":{"bash":{"mode":"allowlist","allow":["git"]}}}`
139+ manifest = `{"zutfile":1,"name":"test","permissions":{"bash":{"mode":"allowlist","allow":["git"]}}}`
140140 }
141- dir := writeTestZotfile(t, manifest)
142- if _, _, err := loadZotfile(dir); err != nil {
141+ dir := writeTestZutfile(t, manifest)
142+ if _, _, err := loadZutfile(dir); err != nil {
143143 t.Fatalf("mode %q rejected: %v", mode, err)
144144 }
145145 }
146- dir := writeTestZotfile(t, `{"zotfile":1,"name":"test","permissions":{"bash":{"mode":"all"}}}`)
147- if _, _, err := loadZotfile(dir); err == nil || !strings.Contains(err.Error(), "unsupported bash permission mode") {
146+ dir := writeTestZutfile(t, `{"zutfile":1,"name":"test","permissions":{"bash":{"mode":"all"}}}`)
147+ if _, _, err := loadZutfile(dir); err == nil || !strings.Contains(err.Error(), "unsupported bash permission mode") {
148148 t.Fatalf("unexpected error for all mode: %v", err)
149149 }
150- dir = writeTestZotfile(t, `{"zotfile":1,"name":"test","permissions":{"bash":{"mode":"everything"}}}`)
151- if _, _, err := loadZotfile(dir); err == nil || !strings.Contains(err.Error(), "unsupported bash permission mode") {
150+ dir = writeTestZutfile(t, `{"zutfile":1,"name":"test","permissions":{"bash":{"mode":"everything"}}}`)
151+ if _, _, err := loadZutfile(dir); err == nil || !strings.Contains(err.Error(), "unsupported bash permission mode") {
152152 t.Fatalf("unexpected error for unknown mode: %v", err)
153153 }
154154 }
155155
156-func TestLoadZotfileRejectsUnsafeOrCollidingNames(t *testing.T) {
156+func TestLoadZutfileRejectsUnsafeOrCollidingNames(t *testing.T) {
157157 for _, name := range []string{"...", "Name", "two words", "a/b"} {
158- dir := writeTestZotfile(t, `{"zotfile":1,"name":"`+name+`"}`)
159- if _, _, err := loadZotfile(dir); err == nil {
158+ dir := writeTestZutfile(t, `{"zutfile":1,"name":"`+name+`"}`)
159+ if _, _, err := loadZutfile(dir); err == nil {
160160 t.Fatalf("unsafe manifest name %q was accepted", name)
161161 }
162162 }
163163 }
164164
165-func TestResolveZotfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
165+func TestResolveZutfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
166166 dir := t.TempDir()
167167 t.Chdir(dir)
168168 if err := os.Mkdir("local-agent", 0o700); err != nil {
169169 t.Fatal(err)
170170 }
171- if err := os.WriteFile("packed-agent.zot", []byte("test"), 0o600); err != nil {
171+ if err := os.WriteFile("packed-agent.zut", []byte("test"), 0o600); err != nil {
172172 t.Fatal(err)
173173 }
174174
@@ -177,9 +177,9 @@ func TestResolveZotfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
177177 want string
178178 }{
179179 {"local-agent", "local-agent"},
180- {"packed-agent", "packed-agent.zot"},
180+ {"packed-agent", "packed-agent.zut"},
181181 {"remote-agent", "remote-agent"},
182- {"missing.zot", "missing.zot"},
182+ {"missing.zut", "missing.zut"},
183183 {"agents/remote-agent", "https://github.com/agents/remote-agent"},
184184 {`agents\remote-agent`, "https://github.com/agents/remote-agent"},
185185 {"frkr/zot-archify", "https://github.com/frkr/zot-archify"},
@@ -190,7 +190,7 @@ func TestResolveZotfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
190190 {"https://github.com/acme/agents/example", "https://github.com/acme/agents/example"},
191191 }
192192 for _, tt := range tests {
193- got, err := resolveZotfileRef(tt.ref)
193+ got, err := resolveZutfileRef(tt.ref)
194194 if err != nil {
195195 t.Fatalf("resolve %q: %v", tt.ref, err)
196196 }
@@ -200,8 +200,8 @@ func TestResolveZotfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
200200 }
201201 }
202202
203-func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) {
204- dir := writeTestZotfile(t, `{"zotfile":1,"name":"test"}`)
203+func TestLoadZutfileRejectsBundledExecutableExtension(t *testing.T) {
204+ dir := writeTestZutfile(t, `{"zutfile":1,"name":"test"}`)
205205 ext := filepath.Join(dir, "extensions", "bad")
206206 if err := os.MkdirAll(ext, 0o700); err != nil {
207207 t.Fatal(err)
@@ -209,31 +209,31 @@ func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) {
209209 if err := os.WriteFile(filepath.Join(ext, "extension.json"), []byte(`{"name":"bad"}`), 0o600); err != nil {
210210 t.Fatal(err)
211211 }
212- if _, _, err := loadZotfile(dir); err == nil || !strings.Contains(err.Error(), "cannot yet be confined") {
212+ if _, _, err := loadZutfile(dir); err == nil || !strings.Contains(err.Error(), "cannot yet be confined") {
213213 t.Fatalf("unexpected error: %v", err)
214214 }
215215 }
216216
217-func TestCheckZotfileMinVersion(t *testing.T) {
218- var zf zotfileLoaded
219- zf.Manifest.Runtime.MinZot = "0.3.0"
220- if err := checkZotfileRequirements(zf, "0.2.75"); err == nil {
221- t.Fatal("old zot version accepted")
217+func TestCheckZutfileMinVersion(t *testing.T) {
218+ var zf zutfileLoaded
219+ zf.Manifest.Runtime.MinZut = "0.3.0"
220+ if err := checkZutfileRequirements(zf, "0.2.75"); err == nil {
221+ t.Fatal("old zut version accepted")
222222 }
223- if err := checkZotfileRequirements(zf, "0.3.0"); err != nil {
223+ if err := checkZutfileRequirements(zf, "0.3.0"); err != nil {
224224 t.Fatalf("minimum version rejected: %v", err)
225225 }
226226 }
227227
228-func TestApplyZotfileModelRequirementsRejectsUnsupportedFields(t *testing.T) {
229- var m ZotfileManifest
228+func TestApplyZutfileModelRequirementsRejectsUnsupportedFields(t *testing.T) {
229+ var m ZutfileManifest
230230 m.Model.MinTier = "frontier"
231- if err := applyZotfileModelRequirements(&Args{}, m); err == nil {
231+ if err := applyZutfileModelRequirements(&Args{}, m); err == nil {
232232 t.Fatal("unsupported min_tier was ignored")
233233 }
234234 m.Model.MinTier = ""
235235 m.Model.Requires = []string{"audio"}
236- if err := applyZotfileModelRequirements(&Args{}, m); err == nil {
236+ if err := applyZutfileModelRequirements(&Args{}, m); err == nil {
237237 t.Fatal("unsupported capability was ignored")
238238 }
239239 }
@@ -251,7 +251,7 @@ func TestUntarRejectsTraversalAndOversizedEntry(t *testing.T) {
251251 if err := untar(bytes.NewReader(makeTar("../escape", 0)), t.TempDir()); err == nil {
252252 t.Fatal("path traversal accepted")
253253 }
254- if err := untar(bytes.NewReader(makeTar("large", maxZotfileEntrySize+1)), t.TempDir()); err == nil {
254+ if err := untar(bytes.NewReader(makeTar("large", maxZutfileEntrySize+1)), t.TempDir()); err == nil {
255255 t.Fatal("oversized entry accepted")
256256 }
257257 }
@@ -281,13 +281,13 @@ func TestParseGitHubAgentURL(t *testing.T) {
281281 }
282282 }
283283
284-func TestLoadRemoteZotfileDownloadsTemporaryGitHubArchive(t *testing.T) {
284+func TestLoadRemoteZutfileDownloadsTemporaryGitHubArchive(t *testing.T) {
285285 var archive bytes.Buffer
286286 gz := gzip.NewWriter(&archive)
287287 tw := tar.NewWriter(gz)
288288 files := map[string]string{
289- "agents-main/zot-maintenance/manifest.json": `{"zotfile":1,"name":"zot-maintenance"}`,
290- "agents-main/zot-maintenance/AGENT.md": "Maintain zot.",
289+ "agents-main/zut-maintenance/manifest.json": `{"zutfile":1,"name":"zut-maintenance"}`,
290+ "agents-main/zut-maintenance/AGENT.md": "Maintain zut.",
291291 }
292292 for name, content := range files {
293293 if err := tw.WriteHeader(&tar.Header{Name: name, Mode: 0o600, Size: int64(len(content))}); err != nil {
@@ -313,13 +313,13 @@ func TestLoadRemoteZotfileDownloadsTemporaryGitHubArchive(t *testing.T) {
313313 githubArchiveURL = func(_, _, _ string) string { return server.URL }
314314 t.Cleanup(func() { githubArchiveURL = oldArchiveURL })
315315
316- u, _ := url.Parse("https://github.com/acme/agents/zot-maintenance")
317- zf, cleanup, err := loadRemoteZotfile(u)
316+ u, _ := url.Parse("https://github.com/acme/agents/zut-maintenance")
317+ zf, cleanup, err := loadRemoteZutfile(u)
318318 if err != nil {
319319 t.Fatal(err)
320320 }
321- if zf.Manifest.Name != "zot-maintenance" || !zf.Temp {
322- t.Fatalf("unexpected zotfile: %+v", zf)
321+ if zf.Manifest.Name != "zut-maintenance" || !zf.Temp {
322+ t.Fatalf("unexpected zutfile: %+v", zf)
323323 }
324324 root := filepath.Dir(filepath.Dir(zf.Dir))
325325 cleanup()
@@ -328,19 +328,19 @@ func TestLoadRemoteZotfileDownloadsTemporaryGitHubArchive(t *testing.T) {
328328 }
329329 }
330330
331-func TestFormatZotfileConsentUsesThemeColors(t *testing.T) {
331+func TestFormatZutfileConsentUsesThemeColors(t *testing.T) {
332332 th := tui.Theme{
333333 FG: tui.Color256(1), Muted: tui.Color256(2), Accent: tui.Color256(3),
334334 Assistant: tui.Color256(4), Warning: tui.Color256(5),
335335 }
336- zf := zotfileLoaded{}
336+ zf := zutfileLoaded{}
337337 zf.Manifest.Name = "reviewer"
338338 zf.Manifest.Version = "1.2.3"
339339 perms := tools.PermissionSet{}
340340 perms.FS.Read = []string{"/workspace"}
341341 perms.Bash.Mode = "ask"
342342
343- got := formatZotfileConsent(zf, perms, th, true)
343+ got := formatZutfileConsent(zf, perms, th, true)
344344 for _, want := range []string{
345345 th.FGColor(th.Assistant, "Agent"),
346346 th.FGColor(th.Accent, "reviewer@1.2.3"),
@@ -377,13 +377,13 @@ func TestPeelLeadingYes(t *testing.T) {
377377 }
378378 }
379379
380-func TestConsentZotfileAutoYesWritesReceipt(t *testing.T) {
380+func TestConsentZutfileAutoYesWritesReceipt(t *testing.T) {
381381 home := t.TempDir()
382- t.Setenv("ZOT_HOME", home)
383- zf := zotfileLoaded{Digest: "abc123", Manifest: ZotfileManifest{Name: "demo", Version: "1.0.0"}}
382+ t.Setenv("ZUT_HOME", home)
383+ zf := zutfileLoaded{Digest: "abc123", Manifest: ZutfileManifest{Name: "demo", Version: "1.0.0"}}
384384 var perms tools.PermissionSet
385385 perms.Bash.Mode = "none"
386- allowed, err := consentZotfile(zf, perms, true)
386+ allowed, err := consentZutfile(zf, perms, true)
387387 if err != nil || !allowed {
388388 t.Fatalf("auto yes: allowed=%v err=%v", allowed, err)
389389 }
@@ -392,19 +392,19 @@ func TestConsentZotfileAutoYesWritesReceipt(t *testing.T) {
392392 t.Fatalf("consent receipt missing: %v", err)
393393 }
394394 // Second call without -y should use the receipt.
395- allowed, err = consentZotfile(zf, perms, false)
395+ allowed, err = consentZutfile(zf, perms, false)
396396 if err != nil || !allowed {
397397 t.Fatalf("cached consent: allowed=%v err=%v", allowed, err)
398398 }
399399 }
400400
401-func TestConsentZotfileAutoYesAskDoesNotCache(t *testing.T) {
401+func TestConsentZutfileAutoYesAskDoesNotCache(t *testing.T) {
402402 home := t.TempDir()
403- t.Setenv("ZOT_HOME", home)
404- zf := zotfileLoaded{Digest: "askdig", Manifest: ZotfileManifest{Name: "ask-agent", Version: "1.0.0"}}
403+ t.Setenv("ZUT_HOME", home)
404+ zf := zutfileLoaded{Digest: "askdig", Manifest: ZutfileManifest{Name: "ask-agent", Version: "1.0.0"}}
405405 var perms tools.PermissionSet
406406 perms.Bash.Mode = "ask"
407- if _, err := consentZotfile(zf, perms, true); err != nil {
407+ if _, err := consentZutfile(zf, perms, true); err != nil {
408408 t.Fatal(err)
409409 }
410410 path := filepath.Join(home, "agents", "ask-agent", "consents", "askdig.json")
M packages/core/agent.go +1 -1
@@ -10,7 +10,7 @@ import (
1010 "time"
1111 "unicode/utf8"
1212
13- "github.com/patriceckhart/zot/packages/provider"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 // Agent is a stateful conversation bound to a provider client, a model,
M packages/core/agent_context_test.go +1 -1
@@ -3,7 +3,7 @@ package core
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/provider"
6+ "github.com/bnema/zut/packages/provider"
77 )
88
99 func TestAppendUserContextAddsAndPublishesMessage(t *testing.T) {
M packages/core/agent_retry_test.go +1 -1
@@ -10,7 +10,7 @@ import (
1010 "time"
1111 "unicode/utf8"
1212
13- "github.com/patriceckhart/zot/packages/provider"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 type retryFakeClient struct {
M packages/core/compact.go +1 -1
@@ -7,7 +7,7 @@ import (
77 "strings"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 // Compact summarizes the agent's transcript via the LLM and replaces
M packages/core/core_test.go +2 -2
@@ -7,7 +7,7 @@ import (
77 "testing"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 func TestAgentRejectsEmptyPrompt(t *testing.T) {
@@ -42,7 +42,7 @@ func TestAgentSetPromptConfigSwapsPromptAndToolsAtomically(t *testing.T) {
4242
4343 func TestSessionRoundTrip(t *testing.T) {
4444 dir := t.TempDir()
45- os.Setenv("ZOT_HOME", dir)
45+ os.Setenv("ZUT_HOME", dir)
4646
4747 sess, err := NewSession(dir, "/tmp/project", "anthropic", "claude-sonnet-4-5", "test")
4848 if err != nil {
M packages/core/cost.go +1 -1
@@ -1,6 +1,6 @@
11 package core
22
3-import "github.com/patriceckhart/zot/packages/provider"
3+import "github.com/bnema/zut/packages/provider"
44
55 // CostTracker accumulates usage across turns in a session.
66 //
M packages/core/events.go +1 -1
@@ -3,7 +3,7 @@ package core
33 import (
44 "encoding/json"
55
6- "github.com/patriceckhart/zot/packages/provider"
6+ "github.com/bnema/zut/packages/provider"
77 )
88
99 // AgentEvent is the superset of events emitted by an Agent run.
M packages/core/fast_mode_test.go +1 -1
@@ -5,7 +5,7 @@ import (
55 "strings"
66 "testing"
77
8- "github.com/patriceckhart/zot/packages/provider"
8+ "github.com/bnema/zut/packages/provider"
99 )
1010
1111 type fastModeTestClient struct {
M packages/core/intercept_test.go +1 -1
@@ -5,7 +5,7 @@ import (
55 "encoding/json"
66 "testing"
77
8- "github.com/patriceckhart/zot/packages/provider"
8+ "github.com/bnema/zut/packages/provider"
99 )
1010
1111 // recordingTool captures the args it was invoked with so the test
M packages/core/queue_test.go +1 -1
@@ -7,7 +7,7 @@ import (
77 "sync/atomic"
88 "testing"
99
10- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 type queueFakeClient struct {
M packages/core/session.go +4 -4
@@ -16,8 +16,8 @@ import (
1616 "strings"
1717 "time"
1818
19+ "github.com/bnema/zut/packages/provider"
1920 "github.com/google/uuid"
20- "github.com/patriceckhart/zot/packages/provider"
2121 )
2222
2323 // Session is a JSONL-backed conversation transcript tied to a cwd.
@@ -782,7 +782,7 @@ func OpenSession(path string) (*Session, []provider.Message, error) {
782782 //
783783 // Corruption gets into the transcript two ways we know of:
784784 //
785-// - Older zot builds that persisted the assistant tool_use row
785+// - Older zut builds that persisted the assistant tool_use row
786786 // before the tool_result row, then crashed between the two.
787787 // - Abort paths in older builds that didn't drop the mid-turn
788788 // assistant message cleanly.
@@ -1025,7 +1025,7 @@ func firstTextFromMessage(msg provider.Message) string {
10251025
10261026 // PruneEmptySessions deletes session files in cwd's session directory
10271027 // that contain only a meta line (no messages were ever appended).
1028-// Cleans up the backlog of empty stubs created by old zot versions
1028+// Cleans up the backlog of empty stubs created by old zut versions
10291029 // that wrote a meta line at NewSession time and never followed up.
10301030 // Errors are swallowed; the caller treats this as best-effort.
10311031 func PruneEmptySessions(root, cwd string) {
@@ -1255,7 +1255,7 @@ func (s *Session) AppendExtensionState(extension string, state json.RawMessage)
12551255
12561256 // Close flushes and closes the session file. If the session was
12571257 // freshly created in this process and never had any messages
1258-// appended (the user opened zot, looked around, and exited without
1258+// appended (the user opened zut, looked around, and exited without
12591259 // prompting), the file is deleted on close so the sessions list
12601260 // doesn't fill up with empty meta-only stubs.
12611261 func (s *Session) Close() error {
M packages/core/session_deferred_tools_test.go +1 -1
@@ -3,7 +3,7 @@ package core
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/provider"
6+ "github.com/bnema/zut/packages/provider"
77 )
88
99 func TestSessionPreservesActivatedDeferredTools(t *testing.T) {
M packages/core/session_history_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "testing"
55 "time"
66
7- "github.com/patriceckhart/zot/packages/provider"
7+ "github.com/bnema/zut/packages/provider"
88 )
99
1010 func TestReadSessionHistoryRetainsPreCompactionSegments(t *testing.T) {
M packages/core/session_portable.go +10 -10
@@ -13,19 +13,19 @@ import (
1313 "strings"
1414 "time"
1515
16+ "github.com/bnema/zut/packages/provider"
1617 "github.com/google/uuid"
17- "github.com/patriceckhart/zot/packages/provider"
1818 )
1919
2020 // PortableExt is the filesystem extension used for exported sessions.
21-// A ".zotsession" is just a zot JSONL session file with the meta
21+// A ".zutsession" is just a zut JSONL session file with the meta
2222 // header rewritten so the importing user gets fresh ownership.
23-const PortableExt = ".zotsession"
23+const PortableExt = ".zutsession"
2424
2525 // ExportSession writes the session at srcPath to dstPath as a
26-// portable .zotsession file. If dstPath is an existing directory the
26+// portable .zutsession file. If dstPath is an existing directory the
2727 // file is created inside it with a name derived from the session's
28-// meta ("YYYYMMDD-HHMMSS-<first-prompt-excerpt>.zotsession"). The
28+// meta ("YYYYMMDD-HHMMSS-<first-prompt-excerpt>.zutsession"). The
2929 // destination's directory is created if needed. Returns the final
3030 // resolved path so the caller can tell the user where it landed.
3131 //
@@ -72,7 +72,7 @@ func ExportSession(srcPath, dstPath string) (string, error) {
7272
7373 // Scan the rest of the file for the first user message so we can
7474 // build a humane filename. Only reads if dstPath doesn't already
75- // end in .zotsession.
75+ // end in .zutsession.
7676 firstPrompt := ""
7777 if !strings.HasSuffix(strings.ToLower(dstPath), PortableExt) {
7878 if fi, _ := os.Stat(dstPath); fi == nil || fi.IsDir() {
@@ -156,13 +156,13 @@ func ExportSession(srcPath, dstPath string) (string, error) {
156156 return outPath, nil
157157 }
158158
159-// ImportSession copies the .zotsession file at srcPath into the
159+// ImportSession copies the .zutsession file at srcPath into the
160160 // running user's session store under the given root+cwd, rewriting
161161 // the meta's id / cwd / started fields so the imported session is
162162 // owned by the current user / directory / clock. Returns the path
163163 // of the created session file, ready to pass to OpenSession.
164164 //
165-// The imported session is a first-class zot session: it'll show up
165+// The imported session is a first-class zut session: it'll show up
166166 // in /sessions, /jump, and on-disk summaries just like any other.
167167 // Messages and usage rows are preserved verbatim.
168168 func ImportSession(srcPath, root, cwd, version string) (string, error) {
@@ -434,7 +434,7 @@ func writeBranchSession(root, cwd, version string, parent SessionMeta, messages
434434 // Build in a same-directory temporary file. Rename is atomic only when
435435 // the temporary and final paths share a directory; closing the file first
436436 // also ensures no buffered writer or descriptor can expose a partial branch.
437- dst, err := os.CreateTemp(dir, ".zot-branch-*.tmp")
437+ dst, err := os.CreateTemp(dir, ".zut-branch-*.tmp")
438438 if err != nil {
439439 return "", fmt.Errorf("branch: create temp: %w", err)
440440 }
@@ -910,7 +910,7 @@ func firstUserPrompt(src io.Reader) (string, error) {
910910 }
911911 }
912912
913-// filenameFor builds a descriptive .zotsession filename from the
913+// filenameFor builds a descriptive .zutsession filename from the
914914 // session's start time and, when available, an excerpt of the
915915 // first user prompt.
916916 func filenameFor(started time.Time, id, firstPrompt string) string {
M packages/core/session_portable_test.go +4 -4
@@ -7,7 +7,7 @@ import (
77 "strings"
88 "testing"
99
10- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 // TestSessionExportImportRoundTrip writes a few messages to a live
@@ -159,7 +159,7 @@ func TestScanSessionMetaReturnsLatestRowWithoutHydratingMessages(t *testing.T) {
159159 }
160160
161161 // TestExportToFilePath writes to an explicit file path (no directory
162-// guessing) and checks the .zotsession extension is appended when missing.
162+// guessing) and checks the .zutsession extension is appended when missing.
163163 func TestExportToFilePath(t *testing.T) {
164164 root := t.TempDir()
165165 sess, err := NewSession(root, "/cwd", "anthropic", "claude-opus-4-7", "0.0.0-test")
@@ -172,14 +172,14 @@ func TestExportToFilePath(t *testing.T) {
172172 })
173173 _ = sess.Close()
174174
175- // No extension — should add .zotsession.
175+ // No extension — should add .zutsession.
176176 dst := filepath.Join(t.TempDir(), "mysession")
177177 out, err := ExportSession(sess.Path, dst)
178178 if err != nil {
179179 t.Fatal(err)
180180 }
181181 if !strings.HasSuffix(out, PortableExt) {
182- t.Errorf("want .zotsession suffix on %q", out)
182+ t.Errorf("want .zutsession suffix on %q", out)
183183 }
184184 }
185185
M packages/core/session_repair_test.go +1 -1
@@ -3,7 +3,7 @@ package core
33 import (
44 "testing"
55
6- "github.com/patriceckhart/zot/packages/provider"
6+ "github.com/bnema/zut/packages/provider"
77 )
88
99 // TestRepairToolUseResultPairsAppendsStub covers the most common
M packages/core/session_snapshot_test.go +1 -1
@@ -10,7 +10,7 @@ import (
1010 "testing"
1111 "time"
1212
13- "github.com/patriceckhart/zot/packages/provider"
13+ "github.com/bnema/zut/packages/provider"
1414 )
1515
1616 func TestAppendMessageRejectsEmptyContent(t *testing.T) {
M packages/core/session_usage_test.go +1 -1
@@ -4,7 +4,7 @@ import (
44 "testing"
55 "time"
66
7- "github.com/patriceckhart/zot/packages/provider"
7+ "github.com/bnema/zut/packages/provider"
88 )
99
1010 func TestSessionUsageDetailResetsContextAfterCompaction(t *testing.T) {
M packages/core/title.go +1 -1
@@ -6,7 +6,7 @@ import (
66 "strings"
77 "unicode"
88
9- "github.com/patriceckhart/zot/packages/provider"
9+ "github.com/bnema/zut/packages/provider"
1010 )
1111
1212 // SessionTitleMaxChars is the maximum length of an automatically generated
M packages/core/title_test.go +1 -1
@@ -7,7 +7,7 @@ import (
77 "testing"
88 "time"
99
10- "github.com/patriceckhart/zot/packages/provider"
10+ "github.com/bnema/zut/packages/provider"
1111 )
1212
1313 type titleTestClient struct {
M packages/core/tool.go +1 -1
@@ -9,7 +9,7 @@ import (
99 "fmt"
1010 "sort"
1111
12- "github.com/patriceckhart/zot/packages/provider"
12+ "github.com/bnema/zut/packages/provider"
1313 )
1414
1515 // Tool is a capability the agent can invoke.
M packages/ignore/gitignore.go +1 -1
@@ -1,5 +1,5 @@
11 // Package ignore provides a minimal .gitignore matcher shared across
2-// zot. It is intentionally small: enough to drop obvious non-source
2+// zut. It is intentionally small: enough to drop obvious non-source
33 // directories (build outputs, dependency and tool caches) from
44 // recursive walks, not a faithful git reimplementation.
55 package ignore
M packages/provider/amazon_bedrock.go +1 -1
@@ -610,7 +610,7 @@ func (c *bedrockClient) Stream(ctx context.Context, req Request) (<-chan Event,
610610 msg := strings.TrimSpace(string(b))
611611 // A 403 on the bearer route is almost always a region mismatch:
612612 // short-term Bedrock API keys are scoped to the region of the
613- // console session that minted them, but zot defaults to
613+ // console session that minted them, but zut defaults to
614614 // us-east-1. Surface the resolved region and the fix so the user
615615 // is not left guessing why a freshly-copied key is "invalid".
616616 if resp.StatusCode == http.StatusForbidden && c.bearerToken != "" {
M packages/provider/anthropic.go +2 -2
@@ -535,11 +535,11 @@ func (c *anthropicClient) Stream(ctx context.Context, req Request) (<-chan Event
535535 return nil, err
536536 }
537537
538- // Optional debug dump: when $ZOT_DEBUG_ANTHROPIC is a file path
538+ // Optional debug dump: when $ZUT_DEBUG_ANTHROPIC is a file path
539539 // we append every outgoing request body to it, one JSON object
540540 // per line. Useful for diffing turn N vs turn N+1 to understand
541541 // why the cache prefix isn't matching.
542- if dump := os.Getenv("ZOT_DEBUG_ANTHROPIC"); dump != "" {
542+ if dump := os.Getenv("ZUT_DEBUG_ANTHROPIC"); dump != "" {
543543 if f, derr := os.OpenFile(dump, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); derr == nil {
544544 _, _ = f.Write(body)
545545 _, _ = f.Write([]byte{'\n'})
M packages/provider/anthropic_image.go +1 -1
@@ -17,7 +17,7 @@ import (
1717
1818 // anthMaxImageSide is Anthropic's per-image dimension cap when a
1919 // request contains more than one image. Single-image requests are
20-// allowed up to 8000 px, but zot conversations routinely include
20+// allowed up to 8000 px, but zut conversations routinely include
2121 // multiple images (a screenshot here, a tool result image there)
2222 // so we always normalise down to the stricter limit. 2000 px on the
2323 // longest side is well below their cap and still readable for the
M packages/provider/auth/api_key_command_test.go +9 -9
@@ -10,7 +10,7 @@ import (
1010 )
1111
1212 func TestAPIKeyCommandHelperProcess(t *testing.T) {
13- if os.Getenv("ZOT_API_KEY_COMMAND_HELPER") != "1" {
13+ if os.Getenv("ZUT_API_KEY_COMMAND_HELPER") != "1" {
1414 return
1515 }
1616 args := os.Args
@@ -22,9 +22,9 @@ func TestAPIKeyCommandHelperProcess(t *testing.T) {
2222 }
2323 switch args[1] {
2424 case "print":
25- fmt.Print(os.Getenv("ZOT_API_KEY_COMMAND_VALUE"))
25+ fmt.Print(os.Getenv("ZUT_API_KEY_COMMAND_VALUE"))
2626 case "fail":
27- fmt.Fprint(os.Stderr, os.Getenv("ZOT_API_KEY_COMMAND_VALUE"))
27+ fmt.Fprint(os.Stderr, os.Getenv("ZUT_API_KEY_COMMAND_VALUE"))
2828 os.Exit(1)
2929 case "sleep":
3030 time.Sleep(5 * time.Second)
@@ -71,8 +71,8 @@ func resetAPIKeyCommandCache(t *testing.T) {
7171
7272 func TestResolveAPIKeyCommand(t *testing.T) {
7373 resetAPIKeyCommandCache(t)
74- t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")
75- t.Setenv("ZOT_API_KEY_COMMAND_VALUE", "secret-value\r\n")
74+ t.Setenv("ZUT_API_KEY_COMMAND_HELPER", "1")
75+ t.Setenv("ZUT_API_KEY_COMMAND_VALUE", "secret-value\r\n")
7676
7777 got, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("print"))
7878 if err != nil {
@@ -85,7 +85,7 @@ func TestResolveAPIKeyCommand(t *testing.T) {
8585
8686 func TestResolveAPIKeyCommandCachesSuccess(t *testing.T) {
8787 resetAPIKeyCommandCache(t)
88- t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")
88+ t.Setenv("ZUT_API_KEY_COMMAND_HELPER", "1")
8989 countPath := t.TempDir() + string(os.PathSeparator) + "count"
9090 command := apiKeyHelperCommand("count", countPath)
9191
@@ -109,8 +109,8 @@ func TestResolveAPIKeyCommandCachesSuccess(t *testing.T) {
109109
110110 func TestResolveAPIKeyCommandFailureDoesNotExposeOutput(t *testing.T) {
111111 resetAPIKeyCommandCache(t)
112- t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")
113- t.Setenv("ZOT_API_KEY_COMMAND_VALUE", "must-not-appear")
112+ t.Setenv("ZUT_API_KEY_COMMAND_HELPER", "1")
113+ t.Setenv("ZUT_API_KEY_COMMAND_VALUE", "must-not-appear")
114114
115115 _, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("fail"))
116116 if err == nil {
@@ -123,7 +123,7 @@ func TestResolveAPIKeyCommandFailureDoesNotExposeOutput(t *testing.T) {
123123
124124 func TestResolveAPIKeyCommandTimeoutAndOutputLimit(t *testing.T) {
125125 resetAPIKeyCommandCache(t)
126- t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")
126+ t.Setenv("ZUT_API_KEY_COMMAND_HELPER", "1")
127127
128128 t.Run("timeout", func(t *testing.T) {
129129 command := apiKeyHelperCommand("sleep")
M packages/provider/auth/assets/assets.go +4 -4
@@ -1,13 +1,13 @@
1-// Package assets holds static resources embedded in the zot binary.
2-// Currently just the zot logo used by the tui welcome banner.
1+// Package assets holds static resources embedded in the zut binary.
2+// Currently just the zut logo used by the tui welcome banner.
33 package assets
44
55 import _ "embed"
66
7-// LogoPNG is the pixel-art zot `z` logo as PNG bytes.
7+// LogoPNG is the pixel-art zut `z` logo as PNG bytes.
88 // Used by the interactive welcome banner; decoded once and rasterized
99 // to Unicode half-blocks so it renders on any terminal without needing
1010 // inline image support.
1111 //
12-//go:embed zot-logo.png
12+//go:embed zut-logo.png
1313 var LogoPNG []byte
R packages/provider/auth/assets/zot-logo.png → packages/provider/auth/assets/zut-logo.png +0 -0 binary file: no text diff · renamed, 100% similar

binary file: no text diff · renamed, 100% similar

M packages/provider/auth/callback.go +11 -11
@@ -10,7 +10,7 @@ import (
1010 "sync"
1111 "time"
1212
13- "github.com/patriceckhart/zot/packages/provider/auth/assets"
13+ "github.com/bnema/zut/packages/provider/auth/assets"
1414 )
1515
1616 // CallbackResult is what an OAuth callback server returns once the
@@ -40,7 +40,7 @@ func NewCallbackServer(p OAuthProvider, expectedState string) (*CallbackServer,
4040 addr := fmt.Sprintf("%s:%d", p.RedirectHost, p.RedirectPort)
4141 l, err := net.Listen("tcp", addr)
4242 if err != nil {
43- return nil, fmt.Errorf("bind %s: %w (is another zot/claude-code/codex login already running?)", addr, err)
43+ return nil, fmt.Errorf("bind %s: %w (is another zut/claude-code/codex login already running?)", addr, err)
4444 }
4545 cs := &CallbackServer{
4646 l: l,
@@ -123,9 +123,9 @@ func (cs *CallbackServer) deliver(r CallbackResult) {
123123
124124 // ---- static HTML for the callback tab ----
125125
126-// All zot-served pages share a single dark style matching the TUI:
126+// All zut-served pages share a single dark style matching the TUI:
127127 // near-black background (#0a0a0a), white text, Geist Mono type, cyan
128-// accent on the word "zot". Serves every step of both the api-key
128+// accent on the word "zut". Serves every step of both the api-key
129129 // flow and the oauth callback flow.
130130 const monoStyle = `<style>
131131 @import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;600&display=swap');
@@ -155,7 +155,7 @@ const monoStyle = `<style>
155155 letter-spacing: 0.01em;
156156 }
157157 h1 .mark { display: inline-block; width: 1.25rem; }
158- .zot { color: #7ed3fc; }
158+ .zut { color: #7ed3fc; }
159159 .rule { border: 0; border-top: 1px solid #ffffff; margin: 1.5rem 0; }
160160 .muted { color: #9ca3af; }
161161 .mono { font-family: inherit; word-break: break-all; }
@@ -179,9 +179,9 @@ const monoStyle = `<style>
179179 button:hover { background: #0a0a0a; color: #ffffff; }
180180 </style>`
181181
182-// logoTag is the <img> element used at the top of every zot-served
182+// logoTag is the <img> element used at the top of every zut-served
183183 // page. The image bytes are served from /logo.png by the same server.
184-const logoTag = `<img class="logo" src="/logo.png" alt="zot" />`
184+const logoTag = `<img class="logo" src="/logo.png" alt="zut" />`
185185
186186 // serveLogo writes the embedded PNG with appropriate caching headers.
187187 // Shared between the oauth callback server and the api-key login server.
@@ -193,21 +193,21 @@ func serveLogo(w http.ResponseWriter, r *http.Request) {
193193
194194 func oauthSuccessHTML(provider string) string {
195195 p := strings.ToLower(provider)
196- return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zot - logged in</title>` + monoStyle + `</head><body>
196+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zut - logged in</title>` + monoStyle + `</head><body>
197197 ` + logoTag + `
198198 <h1><span class="mark">✓</span> logged in to ` + p + `</h1>
199199 <hr class="rule">
200-<p class="msg"><span class="zot">zot</span> received the callback. you can close this tab.</p>
200+<p class="msg"><span class="zut">zut</span> received the callback. you can close this tab.</p>
201201 </body></html>`
202202 }
203203
204204 func oauthErrorHTML(msg string) string {
205- return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zot - error</title>` + monoStyle + `</head><body>
205+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zut - error</title>` + monoStyle + `</head><body>
206206 ` + logoTag + `
207207 <h1><span class="mark">✗</span> login failed</h1>
208208 <hr class="rule">
209209 <p class="msg mono">` + htmlEscape(msg) + `</p>
210-<p class="muted">go back to <span class="zot">zot</span> and try again.</p>
210+<p class="muted">go back to <span class="zut">zut</span> and try again.</p>
211211 </body></html>`
212212 }
213213
M packages/provider/auth/kimi.go +2 -2
@@ -30,7 +30,7 @@ func RequestKimiDeviceAuthorization(ctx context.Context) (KimiDeviceAuthorizatio
3030 }
3131 req.Header.Set("content-type", "application/x-www-form-urlencoded")
3232 req.Header.Set("accept", "application/json")
33- req.Header.Set("user-agent", "zot")
33+ req.Header.Set("user-agent", "zut")
3434
3535 resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
3636 if err != nil {
@@ -77,7 +77,7 @@ func pollKimiDeviceTokenOnce(ctx context.Context, deviceCode string) (*OAuthToke
7777 }
7878 req.Header.Set("content-type", "application/x-www-form-urlencoded")
7979 req.Header.Set("accept", "application/json")
80- req.Header.Set("user-agent", "zot")
80+ req.Header.Set("user-agent", "zut")
8181 resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
8282 if err != nil {
8383 return nil, 0, fmt.Errorf("kimi token poll: %w", err)
M packages/provider/auth/manager.go +2 -2
@@ -453,10 +453,10 @@ func parseManualCodeInput(s string) (code, state string) {
453453 // (containers, SSH without display forwarding, etc.) instead of
454454 // trying to bind a callback port the user can never reach.
455455 func HasBrowser() bool {
456- if os.Getenv("ZOT_NO_BROWSER") != "" {
456+ if os.Getenv("ZUT_NO_BROWSER") != "" {
457457 return false
458458 }
459- if os.Getenv("ZOT_FORCE_BROWSER") != "" {
459+ if os.Getenv("ZUT_FORCE_BROWSER") != "" {
460460 return true
461461 }
462462 if _, err := os.Stat("/.dockerenv"); err == nil {
M packages/provider/auth/oauth.go +5 -3
@@ -20,7 +20,7 @@ import (
2020 // WARNING: the client ids used here belong to the official first-party
2121 // CLIs (Claude Code for Anthropic, Codex CLI for OpenAI). Using them from
2222 // third-party software is against the respective terms of service and
23-// may be revoked at any time. zot exposes this only behind
23+// may be revoked at any time. zut exposes this only behind
2424 // --experimental-oauth.
2525 type OAuthProvider struct {
2626 Name string
@@ -78,7 +78,7 @@ var (
7878
7979 // Anthropic manual / headless variant: redirects to Anthropic's
8080 // copy-code page instead of a local loopback port, used when there
81- // is no browser on the machine running zot (e.g. inside a Docker
81+ // is no browser on the machine running zut (e.g. inside a Docker
8282 // container or over plain SSH).
8383 AnthropicManualOAuth = OAuthProvider{
8484 Name: "anthropic",
@@ -108,7 +108,9 @@ var (
108108 ExtraAuthArgs: map[string]string{
109109 "id_token_add_organizations": "true",
110110 "codex_cli_simplified_flow": "true",
111- "originator": "zot",
111+ // OpenAI's registered Codex OAuth flow requires this legacy
112+ // originator value; it is not a zut product identifier.
113+ "originator": "zot",
112114 },
113115 }
114116
M packages/provider/auth/server.go +11 -11
@@ -270,11 +270,11 @@ func Redirect(w http.ResponseWriter, r *http.Request, u *url.URL) {
270270 // the browser tab looks like the tui: black on white, monospace, thin
271271 // rules, no rounded boxes, no color.
272272
273-var tpl = template.Must(template.New("index").Parse(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zot login</title>` + monoStyle + `</head><body>
273+var tpl = template.Must(template.New("index").Parse(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zut login</title>` + monoStyle + `</head><body>
274274 ` + logoTag + `
275-<h1><span class="zot">zot</span> login</h1>
275+<h1><span class="zut">zut</span> login</h1>
276276 <hr class="rule">
277-<p>paste an api key for anthropic, openai, kimi, deepseek, or google. <span class="zot">zot</span> probes the provider once, then saves the key to <span class="mono">~/Library/Application Support/zot/auth.json</span>.</p>
277+<p>paste an api key for anthropic, openai, kimi, deepseek, or google. <span class="zut">zut</span> probes the provider once, then saves the key to <span class="mono">~/Library/Application Support/zut/auth.json</span>.</p>
278278 <p>
279279 <a href="/apikey?provider=anthropic">anthropic api key →</a><br>
280280 <a href="/apikey?provider=openai">openai api key →</a><br>
@@ -283,18 +283,18 @@ var tpl = template.Must(template.New("index").Parse(`<!doctype html><html lang="
283283 <a href="/apikey?provider=google">google gemini api key →</a>
284284 </p>
285285 <hr class="rule">
286-<p class="muted">for a subscription login (claude pro/max - chatgpt plus/pro - kimi code - github copilot), close this tab and run /login inside <span class="zot">zot</span>.</p>
286+<p class="muted">for a subscription login (claude pro/max - chatgpt plus/pro - kimi code - github copilot), close this tab and run /login inside <span class="zut">zut</span>.</p>
287287 </body></html>`))
288288
289289 func init() {
290- template.Must(tpl.New("apikey").Parse(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zot login</title>` + monoStyle + `<style>
290+ template.Must(tpl.New("apikey").Parse(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zut login</title>` + monoStyle + `<style>
291291 form { display: flex; flex-direction: column; gap: 0.75rem; }
292292 label { font-size: 0.875rem; }
293293 </style></head><body>
294294 ` + logoTag + `
295-<h1><span class="zot">zot</span> login - {{.Provider}} api key</h1>
295+<h1><span class="zut">zut</span> login - {{.Provider}} api key</h1>
296296 <hr class="rule">
297-<p>paste your {{.Provider}} api key. <span class="zot">zot</span> will probe the provider with it once, then save it if the key is accepted.</p>
297+<p>paste your {{.Provider}} api key. <span class="zut">zut</span> will probe the provider with it once, then save it if the key is accepted.</p>
298298 <form method="POST" action="/apikey">
299299 <input type="hidden" name="provider" value="{{.Provider}}" />
300300 <label for="api_key">api key</label>
@@ -303,19 +303,19 @@ func init() {
303303 </form>
304304 </body></html>`))
305305
306- template.Must(tpl.New("success").Parse(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zot - logged in</title>` + monoStyle + `</head><body>
306+ template.Must(tpl.New("success").Parse(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zut - logged in</title>` + monoStyle + `</head><body>
307307 ` + logoTag + `
308308 <h1><span class="mark">✓</span> logged in to {{.Provider}}</h1>
309309 <hr class="rule">
310310 <p class="msg">method: {{.Method}}</p>
311-<p class="muted"><span class="zot">zot</span> received the callback. you can close this tab and return to the terminal.</p>
311+<p class="muted"><span class="zut">zut</span> received the callback. you can close this tab and return to the terminal.</p>
312312 </body></html>`))
313313
314- template.Must(tpl.New("error").Parse(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zot - error</title>` + monoStyle + `</head><body>
314+ template.Must(tpl.New("error").Parse(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>zut - error</title>` + monoStyle + `</head><body>
315315 ` + logoTag + `
316316 <h1><span class="mark">✗</span> login failed</h1>
317317 <hr class="rule">
318318 <p class="msg mono">{{.Message}}</p>
319-<p class="muted">go back to <span class="zot">zot</span> and try again.</p>
319+<p class="muted">go back to <span class="zut">zut</span> and try again.</p>
320320 </body></html>`))
321321 }
M packages/provider/auth/store.go +2 -2
@@ -1,7 +1,7 @@
11 // Package auth handles credential storage and the login methods
2-// supported by zot: API keys and provider-specific subscription OAuth.
2+// supported by zut: API keys and provider-specific subscription OAuth.
33 //
4-// All credentials live in $ZOT_HOME/auth.json (mode 0600).
4+// All credentials live in $ZUT_HOME/auth.json (mode 0600).
55 package auth
66
77 import (
M packages/provider/catalog_builtin.go +1 -1
@@ -6,7 +6,7 @@ package provider
66 //
77 // This file is data; the values are tracked verbatim from each
88 // vendor's own public pricing / model-list pages. They drift; users
9-// override via $ZOT_HOME/models.json (user entries win).
9+// override via $ZUT_HOME/models.json (user entries win).
1010 //
1111 // Curated entries in models.go (anthropic / openai / openai-codex /
1212 // the small seed list for kimi / deepseek / google) are not duplicated
M packages/provider/extra_models.go +1 -1
@@ -3,7 +3,7 @@ package provider
33 // Hand-curated catalog supplements that the bulk builtin catalog
44 // (catalog_builtin.go) doesn't cover.
55 //
6-// - openai-responses: zot-specific provider id for the public OpenAI
6+// - openai-responses: zut-specific provider id for the public OpenAI
77 // Responses API (api.openai.com/v1/responses). Separate from
88 // `openai` (Chat Completions) and `openai-codex` (ChatGPT subscription).
99 // - kimi/kimi-k2-thinking: matches the Anthropic-Messages endpoint
M packages/provider/extra_providers.go +1 -1
@@ -326,7 +326,7 @@ func NewCloudflareAIGateway(apiKey, baseURL string) Client {
326326
327327 // NewGithubCopilot returns a GitHub Copilot client. The provided
328328 // credential must be a GitHub Personal Access Token (PAT) with Copilot
329-// access enabled; zot trades it for a short-lived Copilot token on
329+// access enabled; zut trades it for a short-lived Copilot token on
330330 // every inference request (cached in memory until ~5min before expiry).
331331 //
332332 // Wire format: OpenAI Chat Completions. Copilot-specific headers
M packages/provider/gemini.go +4 -4
@@ -25,7 +25,7 @@ import (
2525 // consumer Gemini Advanced / Google One AI subscriptions; programmatic
2626 // access requires either an AI Studio API key (this client) or Vertex
2727 // AI / GCP service-account credentials (separate provider, not yet
28-// implemented in zot).
28+// implemented in zut).
2929
3030 const geminiDefaultBaseURL = "https://generativelanguage.googleapis.com"
3131
@@ -54,7 +54,7 @@ func (c *geminiClient) Name() string { return "google" }
5454 // ---- wire types ----
5555 //
5656 // Subset of Gemini's Content / Part / GenerateContentRequest schema.
57-// Only the fields zot actually emits or consumes are declared here.
57+// Only the fields zut actually emits or consumes are declared here.
5858
5959 type gemInlineData struct {
6060 MimeType string `json:"mimeType"`
@@ -352,7 +352,7 @@ func saveGeminiImageToWorkingDir(mimeType string, data []byte) (string, error) {
352352 case "image/gif":
353353 ext = ".gif"
354354 }
355- name := "zot-gemini-image-" + uuid.NewString() + ext
355+ name := "zut-gemini-image-" + uuid.NewString() + ext
356356 path := filepath.Join(".", name)
357357 if err := os.WriteFile(path, data, 0o644); err != nil {
358358 return "", err
@@ -406,7 +406,7 @@ func convertGemToolResultParts(blocks []Content) []gemPart {
406406 return parts
407407 }
408408
409-// geminiThinkingConfig maps zot's reasoning level ("low"/"medium"/"high")
409+// geminiThinkingConfig maps zut's reasoning level ("low"/"medium"/"high")
410410 // to Gemini's thinkingConfig. The right knob depends on the model
411411 // generation: 2.5 family uses thinkingBudget (tokens), 3.x uses
412412 // thinkingLevel (enum). Returns nil when the level is unrecognised.
M packages/provider/gemini_test.go +4 -4
@@ -111,7 +111,7 @@ func TestGeminiStreamInlineImage(t *testing.T) {
111111 t.Fatalf("image=%q %q", ib.MimeType, string(ib.Data))
112112 }
113113 saved, ok := done.Message.Content[2].(TextBlock)
114- if !ok || !strings.Contains(saved.Text, "zot-gemini-image-") || !strings.Contains(saved.Text, ".png") {
114+ if !ok || !strings.Contains(saved.Text, "zut-gemini-image-") || !strings.Contains(saved.Text, ".png") {
115115 t.Fatalf("saved path block=%T %+v", done.Message.Content[2], done.Message.Content[2])
116116 }
117117 path := strings.TrimPrefix(saved.Text, "Saved image: `")
@@ -184,7 +184,7 @@ func TestGeminiBuildRequestSystemAndTools(t *testing.T) {
184184 c := NewGemini("k", "https://example.invalid").(*geminiClient)
185185 wire, _, err := c.buildRequest(Request{
186186 Model: "gemini-2.5-pro",
187- System: "you are zot",
187+ System: "you are zut",
188188 Tools: []Tool{
189189 {Name: "read", Description: "read a file", Schema: json.RawMessage(`{"type":"object","properties":{"path":{"type":"string"}}}`)},
190190 },
@@ -193,7 +193,7 @@ func TestGeminiBuildRequestSystemAndTools(t *testing.T) {
193193 if err != nil {
194194 t.Fatal(err)
195195 }
196- if wire.SystemInstruction == nil || wire.SystemInstruction.Parts[0].Text != "you are zot" {
196+ if wire.SystemInstruction == nil || wire.SystemInstruction.Parts[0].Text != "you are zut" {
197197 t.Fatalf("system: %+v", wire.SystemInstruction)
198198 }
199199 if len(wire.Tools) != 1 || len(wire.Tools[0].FunctionDeclarations) != 1 || wire.Tools[0].FunctionDeclarations[0].Name != "read" {
@@ -537,7 +537,7 @@ func TestGeminiStreamReasoning(t *testing.T) {
537537 }
538538 }
539539
540-// TestGeminiBuildRequestWithReasoningReplay verifies that when zot sends a
540+// TestGeminiBuildRequestWithReasoningReplay verifies that when zut sends a
541541 // historical assistant message containing a ReasoningBlock, buildRequest
542542 // serializes it into the verbatim Gemini "thought: true" wire representation
543543 // with its corresponding thoughtSignature.
M packages/provider/github_copilot.go +2 -2
@@ -5,7 +5,7 @@ package provider
55 // Auth chain:
66 //
77 // 1. User stores a GitHub Personal Access Token (PAT) in
8-// COPILOT_GITHUB_TOKEN (or via zot's auth.json, when wired). The PAT
8+// COPILOT_GITHUB_TOKEN (or via zut's auth.json, when wired). The PAT
99 // is the long-lived credential; it never hits the inference endpoint.
1010 // 2. We trade the PAT for a short-lived Copilot token by hitting
1111 // `GET https://api.github.com/copilot_internal/v2/token` with the PAT
@@ -19,7 +19,7 @@ package provider
1919 // - X-Initiator: user|agent
2020 // - Openai-Intent: conversation-edits
2121 // - Copilot-Vision-Request: true (when images present; not wired
22-// here because the zot openai client currently sends images inline)
22+// here because the zut openai client currently sends images inline)
2323 //
2424 // Token caching: short-lived tokens last ~30min. We cache one per PAT in
2525 // memory for the process lifetime and refresh on demand. No disk cache.
M packages/provider/google_vertex.go +1 -1
@@ -52,7 +52,7 @@ import (
5252 "sync"
5353 "time"
5454
55- providerauth "github.com/patriceckhart/zot/packages/provider/auth"
55+ providerauth "github.com/bnema/zut/packages/provider/auth"
5656 )
5757
5858 type vertexConfig struct {
M packages/provider/llamacpp.go +1 -1
@@ -419,7 +419,7 @@ func (c *LlamaCPPClient) DownloadAndWait(ctx context.Context, model string, upda
419419 }
420420 }
421421
422-// LlamaCPPModels converts loaded router entries into zot model metadata.
422+// LlamaCPPModels converts loaded router entries into zut model metadata.
423423 func LlamaCPPModels(models []LlamaCPPModel, serverURL string) []Model {
424424 baseURL, err := LlamaCPPInferenceURL(serverURL)
425425 if err != nil {
M packages/provider/models.go +1 -1
@@ -60,7 +60,7 @@ type Model struct {
6060 }
6161
6262 // Catalog is the hardcoded, read-only list of supported models.
63-// Prices are USD per 1M tokens. The list is curated to what zot's
63+// Prices are USD per 1M tokens. The list is curated to what zut's
6464 // clients (Anthropic Messages + OpenAI Chat Completions) can actually
6565 // talk to; models that are only reachable through the OpenAI Responses
6666 // API (o1-pro, o3-pro, gpt-5-pro) are omitted.
M packages/provider/openai.go +2 -2
@@ -261,7 +261,7 @@ func (c *openaiClient) buildRequest(req Request) (*oaiRequest, error) {
261261 // Some gateways expose adaptive-thinking Anthropic models through
262262 // the OpenAI-compatible chat-completions wire. They accept the
263263 // same reasoning_effort knob, including the top "xhigh" tier;
264- // don't clamp zot's "maximum" to "high" for those models.
264+ // don't clamp zut's "maximum" to "high" for those models.
265265 effort = OpenAICompatAnthropicEffort(reasoning)
266266 }
267267 if effort != "" {
@@ -341,7 +341,7 @@ func (c *openaiClient) buildRequest(req Request) (*oaiRequest, error) {
341341 }
342342 // Kimi rejects assistant messages with neither visible text nor
343343 // tool calls ("assistant must not be empty"). This can happen when
344- // a previous stream produced only reasoning_content, which zot keeps
344+ // a previous stream produced only reasoning_content, which zut keeps
345345 // internally for provider replay but cannot send back as standalone
346346 // assistant content on OpenAI-compatible chat-completions APIs.
347347 if am.Content == nil && len(am.ToolCalls) == 0 {
M packages/provider/openai_codex.go +5 -3
@@ -21,7 +21,7 @@ import (
2121 // Wire protocol notes:
2222 // - Endpoint: POST https://chatgpt.com/backend-api/codex/responses
2323 // - Headers: Authorization: Bearer <access_token>, chatgpt-account-id: <id>,
24-// OpenAI-Beta: responses=experimental, originator: zot
24+// OpenAI-Beta: responses=experimental, originator: zot (required legacy value)
2525 // - Body: OpenAI Responses API shape (not chat/completions).
2626 // input: [{role, content: [{type: "input_text" | "input_image" | ... }]}]
2727 // instructions: <system prompt>
@@ -75,7 +75,7 @@ func NewOpenAICodex(token, accountID, baseURL string) Client {
7575
7676 func (c *codexClient) Name() string { return "openai-codex" }
7777
78-// ---- Responses API wire types (subset needed for zot's surface) ----
78+// ---- Responses API wire types (subset needed for zut's surface) ----
7979
8080 type codexInputText struct {
8181 Type string `json:"type"` // "input_text"
@@ -360,7 +360,7 @@ func usesCodexCLIRouting(model string) bool {
360360 func newCodexSessionID() string {
361361 var b [16]byte
362362 if _, err := rand.Read(b[:]); err != nil {
363- return fmt.Sprintf("zot-%d", time.Now().UnixNano())
363+ return fmt.Sprintf("zut-%d", time.Now().UnixNano())
364364 }
365365 return strings.Join([]string{
366366 hex.EncodeToString(b[0:4]),
@@ -410,6 +410,8 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
410410 httpReq.Header.Set("session-id", codexCLISessionID)
411411 httpReq.Header.Set("user-agent", "codex_cli_rs/0.0.0")
412412 } else {
413+ // ChatGPT's backend recognizes these established values. They are
414+ // external provider metadata, not zut product identifiers.
413415 httpReq.Header.Set("originator", "zot")
414416 httpReq.Header.Set("user-agent", fmt.Sprintf("zot (%s %s)", runtime.GOOS, runtime.GOARCH))
415417 }
M packages/provider/openai_codex_test.go +7 -0
@@ -6,6 +6,7 @@ import (
66 "encoding/json"
77 "io"
88 "net/http"
9+ "runtime"
910 "strings"
1011 "testing"
1112 )
@@ -175,6 +176,12 @@ func TestOpenAIGPT56DoesNotUseCodexCLIRouting(t *testing.T) {
175176 if gotBody.PromptCacheKey != "" {
176177 t.Fatalf("prompt_cache_key = %q", gotBody.PromptCacheKey)
177178 }
179+ if gotReq.Header.Get("originator") != "zot" {
180+ t.Fatalf("originator = %q", gotReq.Header.Get("originator"))
181+ }
182+ if gotReq.Header.Get("user-agent") != "zot ("+runtime.GOOS+" "+runtime.GOARCH+")" {
183+ t.Fatalf("user-agent = %q", gotReq.Header.Get("user-agent"))
184+ }
178185 }
179186
180187 func TestCodexNestedStreamError(t *testing.T) {
M packages/provider/provider.go +2 -2
@@ -1,4 +1,4 @@
1-// Package provider defines the LLM client abstraction used by zot.
1+// Package provider defines the LLM client abstraction used by zut.
22 //
33 // It supports exactly two providers: Anthropic (Messages API) and
44 // OpenAI (Chat Completions API). Everything above this package operates
@@ -69,7 +69,7 @@ func (ToolResultBlock) isContent() {}
6969 // providers that require it on follow-up requests (OpenAI Codex with
7070 // thinking enabled) can replay the same payload they emitted earlier.
7171 // Summary is the human-readable reasoning summary (may be empty); the
72-// encrypted blob is opaque to zot. ID is the provider-issued reasoning
72+// encrypted blob is opaque to zut. ID is the provider-issued reasoning
7373 // item id.
7474 type ReasoningBlock struct {
7575 ID string `json:"reasoning_id,omitempty"`
M packages/provider/provider_test.go +1 -1 collapsed to keep the page light
M packages/provider/reasoning.go +6 -6 collapsed to keep the page light
M packages/provider/usermodels.go +1 -1 collapsed to keep the page light
M packages/tui/clipboard_darwin.go +2 -2 collapsed to keep the page light
M packages/tui/clipboard_text_linux.go +1 -1 collapsed to keep the page light
M packages/tui/detect_bg.go +1 -1 collapsed to keep the page light
M packages/tui/highlight.go +1 -1 collapsed to keep the page light
M packages/tui/image.go +12 -12 collapsed to keep the page light
M packages/tui/image_test.go +6 -6 collapsed to keep the page light
M packages/tui/markdown_test.go +2 -2 collapsed to keep the page light
M packages/tui/partialjson.go +1 -1 collapsed to keep the page light
M packages/tui/render.go +7 -7 collapsed to keep the page light
M packages/tui/statusbar_test.go +1 -1 collapsed to keep the page light
M packages/tui/theme.go +7 -7 collapsed to keep the page light
M packages/tui/theme_loader.go +16 -16 collapsed to keep the page light
M packages/tui/title_test.go +1 -1 collapsed to keep the page light
M packages/tui/view.go +13 -13
@@ -9,8 +9,8 @@ import (
99 "strings"
1010 "unicode/utf8"
1111
12+ "github.com/bnema/zut/packages/provider"
1213 "github.com/mattn/go-runewidth"
13- "github.com/patriceckhart/zot/packages/provider"
1414 )
1515
1616 // expandTabs replaces tab characters with 4 spaces so code from
@@ -194,7 +194,7 @@ type msgCacheKey struct {
194194 expandAll bool
195195 // turnOpen is true when the previous rendered message belongs to
196196 // the same agent turn (assistant tool_use, or tool result). The
197- // header ("▍ zot") is suppressed in that case so a single turn
197+ // header ("▍ zut") is suppressed in that case so a single turn
198198 // — even one that spans many assistant/tool message round-trips
199199 // in the underlying API — renders under one header instead of a
200200 // new one per assistant message.
@@ -405,7 +405,7 @@ func (v *View) BuildWithAnchors(width int) ([]string, []MessageAnchor) {
405405 // to the most recent user prompt. Walk back over consecutive
406406 // assistant/tool messages: if any non-user message precedes
407407 // this one without a user message in between, we're inside
408- // the same turn and should not draw a new "▍ zot" header.
408+ // the same turn and should not draw a new "▍ zut" header.
409409 turnOpen := false
410410 if m.Role == provider.RoleAssistant {
411411 for j := idx - 1; j >= 0; j-- {
@@ -448,7 +448,7 @@ func (v *View) BuildWithAnchors(width int) ([]string, []MessageAnchor) {
448448 // text to show. An empty streaming block (streamOn=true,
449449 // Streaming="") appears when a turn starts with a tool_use
450450 // block instead of text — in that case the live tool-call
451- // overlay below is the real content and a naked "zot" bar
451+ // overlay below is the real content and a naked "zut" bar
452452 // above it reads as a stray empty message.
453453 if v.StreamingActive && strings.TrimSpace(v.Streaming) != "" {
454454 // Stream the partial assistant text through the same markdown
@@ -741,7 +741,7 @@ func (v *View) renderMessage(m provider.Message, width int, turnOpen bool) []str
741741 case provider.RoleAssistant:
742742 // Assistant rows: no speaker label either. Prose still gets a
743743 // small left indent so it visually aligns with tool box body
744- // content, but no "zot" header.
744+ // content, but no "zut" header.
745745 _ = turnOpen
746746 const indent = " "
747747 inner := assistantBodyWidth(width - len(indent))
@@ -867,7 +867,7 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
867867
868868 // Live body (write/edit): keep the streamed preview visible until
869869 // a proposed or real tool result arrives. The provider can finish the
870- // tool_use JSON before zot has executed the tool, so keying this on
870+ // tool_use JSON before zut has executed the tool, so keying this on
871871 // tc.Streaming makes write/edit boxes collapse for a moment between
872872 // EvToolUseEnd and confirmation or EvToolResult.
873873 if tc.Preview == "" && tc.Result == "" {
@@ -1746,7 +1746,7 @@ func wrapCodeLine(line string, width int) []string {
17461746
17471747 // renderImageBlock returns the lines for one image, inline if possible.
17481748 //
1749-// Inline image escapes paint into multiple terminal rows but the zot
1749+// Inline image escapes paint into multiple terminal rows but the zut
17501750 // renderer treats each slice entry as a single row. To prevent chat
17511751 // content from being drawn on top of the image, we pad with blank rows
17521752 // so the image's real footprint is reflected in the frame height.
@@ -2193,19 +2193,19 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string {
21932193
21942194 // defaultToolArgWidth is the number of cells the tool-call header
21952195 // truncates the primary argument (path/command/query) to when the
2196-// ZOT_TOOL_ARG_WIDTH environment variable is unset or invalid.
2196+// ZUT_TOOL_ARG_WIDTH environment variable is unset or invalid.
21972197 const defaultToolArgWidth = 60
21982198
21992199 // toolArgWidth returns the maximum cell width for the truncated
22002200 // primary argument shown in a tool-call header. It defaults to
22012201 // defaultToolArgWidth but can be raised or lowered with the
2202-// ZOT_TOOL_ARG_WIDTH environment variable, which is useful for wide
2202+// ZUT_TOOL_ARG_WIDTH environment variable, which is useful for wide
22032203 // terminals where the default clips long queries too aggressively.
22042204 // Values outside the sane range [20, 500] are ignored so a stray or
22052205 // malformed setting can never produce an unreadable header or an
22062206 // out-of-range slice.
22072207 func toolArgWidth() int {
2208- v := strings.TrimSpace(os.Getenv("ZOT_TOOL_ARG_WIDTH"))
2208+ v := strings.TrimSpace(os.Getenv("ZUT_TOOL_ARG_WIDTH"))
22092209 if v == "" {
22102210 return defaultToolArgWidth
22112211 }
@@ -2225,7 +2225,7 @@ func toolArgWidth() int {
22252225 // the legacy "path or command, truncated" shape.
22262226 //
22272227 // The truncation width defaults to 60 cells but can be tuned via
2228-// the ZOT_TOOL_ARG_WIDTH environment variable (see toolArgWidth).
2228+// the ZUT_TOOL_ARG_WIDTH environment variable (see toolArgWidth).
22292229 //
22302230 // Exported because the interactive mode pre-populates the
22312231 // ToolCallView.Args field with this value as soon as the tool
@@ -2451,7 +2451,7 @@ func StatusBar(p StatusBarParams) []string {
24512451 }
24522452
24532453 // Layout uses exactly 2 spaces of horizontal padding everywhere:
2454- // 2 spaces (openai) gpt-5.4 $0.000 (sub) 0.0%/400k ~/Sites/zot
2454+ // 2 spaces (openai) gpt-5.4 $0.000 (sub) 0.0%/400k ~/Sites/zut
24552455 // matches the editor prompt's left inset so the bar lines up
24562456 // vertically with the conversation column.
24572457 const pad = " " // 2 spaces
@@ -2493,7 +2493,7 @@ func StatusBar(p StatusBarParams) []string {
24932493 } else {
24942494 // Idle path: a single pad of left inset so the line
24952495 // aligns with the conversation column on its left edge
2496- // (" you" / " zot" message markers). Without the busy
2496+ // (" you" / " zut" message markers). Without the busy
24972497 // prefix there's no trailing separator to double-pad.
24982498 leftBuilder.WriteString(pad)
24992499 }
M packages/tui/view_compact_mode_test.go +1 -1 collapsed to keep the page light
M packages/tui/view_compact_user_test.go +1 -1 collapsed to keep the page light
M packages/tui/view_flat_tools_test.go +1 -1 collapsed to keep the page light
M packages/tui/view_tool_arg_width_test.go +6 -6 collapsed to keep the page light
M packages/tui/view_tool_overlay_test.go +3 -3 collapsed to keep the page light

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