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:
24 attributes:24 attributes:
25 label: Expected behavior25 label: Expected behavior
26 description: What should have happened instead?26 description: What should have happened instead?
27- placeholder: "I expected zot to..."27+ placeholder: "I expected zut to..."
28 validations:28 validations:
29 required: true29 required: true
30 30
@@ -32,7 +32,7 @@ body:
32 id: version32 id: version
33 attributes:33 attributes:
34 label: Version34 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"
36 validations:36 validations:
37 required: true37 required: true
38 38
M .github/ISSUE_TEMPLATE/feature_request.yml +2 -2
@@ -8,7 +8,7 @@ body:
8 value: |8 value: |
9 Thanks for the idea! A short conversation here saves everyone time before any code is written.9 Thanks for the idea! A short conversation here saves everyone time before any code is written.
10 10
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.
12 12
13 - type: textarea13 - type: textarea
14 id: problem14 id: problem
@@ -16,7 +16,7 @@ body:
16 label: What problem does this solve?16 label: What problem does this solve?
17 description: |17 description: |
18 The **why**, not the **what**. What were you trying to do when you wished this existed?18 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."
20 validations:20 validations:
21 required: true21 required: true
22 22
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:
53 steps:53 steps:
54 - uses: actions/checkout@v454 - uses: actions/checkout@v4
55 55
56+ - name: release-version policy
57+ run: bash .github/scripts/next-release-tag_test.sh
58+
56 - name: set up go59 - name: set up go
57 uses: actions/setup-go@v760 uses: actions/setup-go@v7
58 with:61 with:
M .github/workflows/release.yml +36 -71
@@ -1,18 +1,8 @@
1 name: release1 name: release
2 2
3-# Every push to main that passes CI cuts a new release. The workflow:3+# After CI succeeds on main, create at most one tag for the unreleased
4-# 1. waits for the ci workflow on the same commit to succeed4+# conventional commits. The tag is then built by the shared GoReleaser
5-# 2. looks up the latest existing vX.Y.Z tag5+# workflow at its exact commit.
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.
16 on:6 on:
17 workflow_run:7 workflow_run:
18 workflows: [ci]8 workflows: [ci]
@@ -20,85 +10,60 @@ on:
20 branches: [main]10 branches: [main]
21 11
22 permissions:12 permissions:
23- # Needed to create tags and to upload release archives.
24 contents: write13 contents: write
25 14
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.
28 concurrency:15 concurrency:
29 group: release16 group: release
30 cancel-in-progress: false17 cancel-in-progress: false
31 18
32 jobs:19 jobs:
33- release:20+ tag:
34- # Only run when ci succeeded and we're on main, and when the head21+ if: >-
35- # commit doesn't opt out via [skip-release].22+ github.event.workflow_run.conclusion == 'success' &&
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]') }}23+ github.event.workflow_run.head_branch == 'main' &&
24+ !contains(github.event.workflow_run.head_commit.message, '[release=skip]')
37 runs-on: ubuntu-latest25 runs-on: ubuntu-latest
26+ outputs:
27+ tag: ${{ steps.version.outputs.tag }}
38 steps:28 steps:
39- - name: checkout29+ - name: Check out the verified commit
40 uses: actions/checkout@v430 uses: actions/checkout@v4
41 with:31 with:
42- # Full history so we can read existing tags.
43 fetch-depth: 032 fetch-depth: 0
44- # Check out the exact sha that ci verified, not whatever the33+ fetch-tags: true
45- # branch tip happens to be at this moment (which may have
46- # advanced since ci finished).
47 ref: ${{ github.event.workflow_run.head_sha }}34 ref: ${{ github.event.workflow_run.head_sha }}
48 35
49- - name: compute next version36+ - name: Compute the next release tag
50- id: bump37+ id: version
51 shell: bash38 shell: bash
52 run: |39 run: |
53 set -euo pipefail40 set -euo pipefail
54- # Find the newest vX.Y.Z tag. If the repo has none yet, start41+ tag=$(bash .github/scripts/next-release-tag.sh)
55- # at v0.0.0 so the first release becomes v0.0.1.42+ if [ -z "$tag" ]; then
56- latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1)43+ echo "No releasable conventional commits for this run."
57- if [ -z "$latest" ]; then44+ exit 0
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))
72 fi45 fi
73- next="v${major}.${minor}.${patch}"46+ echo "Releasing $tag from $(git rev-parse --short HEAD)"
74- echo "next tag: $next"47+ echo "tag=$tag" >> "$GITHUB_OUTPUT"
75 48
76- echo "next=$next" >> "$GITHUB_OUTPUT"49+ - name: Create and push the tag
77- echo "version=${next#v}" >> "$GITHUB_OUTPUT"50+ if: steps.version.outputs.tag != ''
78-
79- - name: create and push tag
80 env:51 env:
81- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}52+ TAG: ${{ steps.version.outputs.tag }}
82 run: |53 run: |
83 git config user.name "github-actions[bot]"54 git config user.name "github-actions[bot]"
84 git config user.email "github-actions[bot]@users.noreply.github.com"55 git config user.email "github-actions[bot]@users.noreply.github.com"
85- git tag -a "${{ steps.bump.outputs.next }}" \56+ git tag -a "$TAG" -m "release $TAG"
86- -m "release ${{ steps.bump.outputs.next }}"57+ git push origin "$TAG"
87- git push origin "${{ steps.bump.outputs.next }}"
88 58
89- - name: set up go59+ release:
90- uses: actions/setup-go@v760+ needs: tag
91- with:61+ if: needs.tag.outputs.tag != ''
92- go-version: "1.26"62+ permissions:
93- cache: true63+ contents: write
94-64+ uses: bnema/gh-actions/.github/workflows/go-release.yml@main
95- - name: run goreleaser65+ with:
96- uses: goreleaser/goreleaser-action@v666+ go-version: "1.26"
97- with:67+ # The reusable workflow fetches tags before this command, ensuring
98- distribution: goreleaser68+ # GoReleaser produces artifacts and notes for the newly created tag.
99- version: latest69+ pre-command: git checkout --detach "${{ needs.tag.outputs.tag }}"
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 }}
M .goreleaser.yaml +38 -33
@@ -1,23 +1,18 @@
1 # GoReleaser config — builds cross-platform binaries and GitHub Release1 # GoReleaser config — builds cross-platform binaries and GitHub Release
2-# artifacts for zot.2+# artifacts for zut. The release workflow creates conventional-commit-based
3-#3+# v0.x tags, then invokes GoReleaser for that immutable tag.
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-
9 version: 24 version: 2
10 5
11-project_name: zot6+project_name: zut
12 7
13 before:8 before:
14 hooks:9 hooks:
15 - go mod tidy10 - go mod tidy
16 11
17 builds:12 builds:
18- - id: zot13+ - id: zut
19- main: ./cmd/zot14+ main: ./cmd/zut
20- binary: zot15+ binary: zut
21 env:16 env:
22 - CGO_ENABLED=017 - CGO_ENABLED=0
23 goos: [linux, darwin, windows]18 goos: [linux, darwin, windows]
@@ -36,59 +31,69 @@ builds:
36 - -X main.date={{.Date}}31 - -X main.date={{.Date}}
37 32
38 archives:33 archives:
39- - id: zot34+ - id: zut
40- # Human-friendly name: zot_0.1.0_darwin_arm64.tar.gz35+ # Human-friendly name: zut_0.1.0_darwin_arm64.tar.gz
41 name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"36 name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
42 files:37 files:
43 - LICENSE38 - LICENSE
44 - README.md39 - README.md
45 format_overrides:40 format_overrides:
46 - goos: windows41 - goos: windows
47- format: zip42+ formats: [zip]
48 43
49 checksum:44 checksum:
50 name_template: "checksums.txt"45 name_template: "checksums.txt"
51 algorithm: sha25646 algorithm: sha256
52 47
53 snapshot:48 snapshot:
54- # Used by `goreleaser release --snapshot`; lets contributors build
55- # a local release without pushing a tag.
56 version_template: "{{ incpatch .Version }}-next"49 version_template: "{{ incpatch .Version }}-next"
57 50
51+git:
52+ # Generate prerelease changelogs from the previous stable tag.
53+ tag_sort: smartsemver
54+
58 changelog:55 changelog:
59 sort: asc56 sort: asc
60 use: github57 use: github
61 filters:58 filters:
59+ # Styling-only commits are neither releasable nor useful release notes.
62 exclude:60 exclude:
63- - "^docs:"61+ - "^style:"
64- - "^chore:"
65- - "^ci:"
66- - "^test:"
67 groups:62 groups:
68- - title: features63+ - title: Added
69- regexp: "^.*feat[(\\w)]*:+.*$"64+ regexp: "^feat(\\([^)]+\\))?!?:"
70 order: 065 order: 0
71- - title: fixes66+ - title: Fixed
72- regexp: "^.*fix[(\\w)]*:+.*$"67+ regexp: "^fix(\\([^)]+\\))?!?:"
73 order: 168 order: 1
74- - title: other69+ - 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
75 order: 99982 order: 999
76 83
77 release:84 release:
78 github:85 github:
79- owner: patriceckhart86+ owner: bnema
80- name: zot87+ name: zut
81 draft: false88 draft: false
82 prerelease: auto89 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.
85 header: |90 header: |
86- ## zot {{ .Tag }}91+ ## zut {{ .Tag }}
87 92
88- one-liner install:93+ Install with:
89 94
90 ```bash95 ```bash
91- curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash96+ curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash
92 ```97 ```
93 98
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 zot1+# Working Agreement for zut
2 2
3 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.3 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.
4 4
5 ## Product intent5 ## Product intent
6 6
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:
8 8
9 - one portable binary9 - one portable binary
10 - a provider-neutral agent engine10 - a provider-neutral agent engine
@@ -36,7 +36,7 @@ Put behavior in the package that owns the concern:
36 | `packages/core` | Agent loop, messages, tool contracts, events, confirmation, sessions, compaction |36 | `packages/core` | Agent loop, messages, tool contracts, events, confirmation, sessions, compaction |
37 | `packages/provider` | Provider clients, wire formats, streaming, retries, model metadata and discovery |37 | `packages/provider` | Provider clients, wire formats, streaming, retries, model metadata and discovery |
38 | `packages/provider/auth` | Login flows, credential lookup, refresh, and storage |38 | `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 |
40 | `packages/agent/modes` | Interactive, print, JSON, dialog, bot, and Telegram interfaces |40 | `packages/agent/modes` | Interactive, print, JSON, dialog, bot, and Telegram interfaces |
41 | `packages/agent/extensions` | Extension process lifecycle and host integration |41 | `packages/agent/extensions` | Extension process lifecycle and host integration |
42 | `packages/agent/extproto` | Extension wire protocol |42 | `packages/agent/extproto` | Extension wire protocol |
@@ -51,7 +51,7 @@ Provider quirks must not leak into `packages/core`. Terminal escape handling and
51 51
52 ## Correctness contracts52 ## Correctness contracts
53 53
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.
55 55
56 ### Agent events and sessions56 ### Agent events and sessions
57 57
@@ -148,13 +148,13 @@ Documentation-only changes do not require the Go test suite unless they alter ge
148 148
149 ## Documentation duties149 ## Documentation duties
150 150
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`.
152 152
153 Examples must match real flags and schemas. State limitations plainly, especially around sandboxing, provider support, compatibility, and platform differences.153 Examples must match real flags and schemas. State limitations plainly, especially around sandboxing, provider support, compatibility, and platform differences.
154 154
155 ## Dependency policy155 ## Dependency policy
156 156
157-zot's single-binary design and small footprint are product requirements.157+zut's single-binary design and small footprint are product requirements.
158 158
159 - Prefer the standard library or existing dependencies.159 - Prefer the standard library or existing dependencies.
160 - Explain why a new dependency is necessary before adding it.160 - Explain why a new dependency is necessary before adding it.
M CONTRIBUTING.md +9 -9
@@ -1,4 +1,4 @@
1-# Contributing to zot1+# Contributing to zut
2 2
3 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.3 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.
4 4
@@ -8,7 +8,7 @@ Contributions are welcome, including bug reports, design feedback, documentation
8 8
9 Use the bug-report template and provide enough information for someone else to reproduce the problem:9 Use the bug-report template and provide enough information for someone else to reproduce the problem:
10 10
11-- the output of `zot --version`11+- the output of `zut --version`
12 - operating system and terminal12 - operating system and terminal
13 - provider and model, when relevant13 - provider and model, when relevant
14 - the smallest sequence of actions that triggers the problem14 - 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
35 35
36 ## Design boundaries36 ## Design boundaries
37 37
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.
39 39
40 The major ownership boundaries are:40 The major ownership boundaries are:
41 41
42 - `packages/core`: provider-independent agent behavior and session structures42 - `packages/core`: provider-independent agent behavior and session structures
43 - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior43 - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior
44 - `packages/provider/auth`: credential and login handling44 - `packages/provider/auth`: credential and login handling
45-- `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and zotfiles45+- `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and zutfiles
46 - `packages/agent/modes`: interactive and non-interactive user modes46 - `packages/agent/modes`: interactive and non-interactive user modes
47 - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK47 - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK
48 - `packages/agent/skills`: reusable instruction discovery48 - `packages/agent/skills`: reusable instruction discovery
@@ -62,14 +62,14 @@ Use the Go version declared in `go.mod`, then build from the repository root:
62 ```sh62 ```sh
63 go mod download63 go mod download
64 make build64 make build
65-./bin/zot --version65+./bin/zut --version
66 ```66 ```
67 67
68 Useful commands:68 Useful commands:
69 69
70 ```sh70 ```sh
71 make help # list the important developer commands71 make help # list the important developer commands
72-make build # compile ./bin/zot as 0.0.0-dev72+make build # compile ./bin/zut as 0.0.0-dev
73 make run # build and start the interactive application73 make run # build and start the interactive application
74 make install # install the current checkout with go install74 make install # install the current checkout with go install
75 make go-install # install a published module version75 make go-install # install a published module version
@@ -79,7 +79,7 @@ make lint # run golangci-lint, go vet, and verify gofmt
79 make lint-install # install the pinned golangci-lint version79 make lint-install # install the pinned golangci-lint version
80 ```80 ```
81 81
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.
83 83
84 ## Making a change84 ## Making a change
85 85
@@ -150,7 +150,7 @@ If a validation command cannot pass because of an unrelated repository or enviro
150 Open the pull request against `main` and include:150 Open the pull request against `main` and include:
151 151
152 - the problem being addressed152 - the problem being addressed
153-- the chosen solution and why it belongs in zot153+- the chosen solution and why it belongs in zut
154 - tests added or updated154 - tests added or updated
155 - commands used for verification155 - commands used for verification
156 - user-visible, compatibility, and platform effects156 - user-visible, compatibility, and platform effects
@@ -170,7 +170,7 @@ Choose the document that owns the public surface:
170 - `docs/extensions.md` for extension authors170 - `docs/extensions.md` for extension authors
171 - `docs/skills.md` for reusable skills171 - `docs/skills.md` for reusable skills
172 - `docs/themes.md` for theme files172 - `docs/themes.md` for theme files
173-- `docs/zotfiles.md` for portable agents173+- `docs/zutfiles.md` for portable agents
174 174
175 Examples should be runnable and limitations should be stated directly.175 Examples should be runnable and limitations should be stated directly.
176 176
M Makefile +5 -5
@@ -3,8 +3,8 @@
3 .DEFAULT_GOAL := help3 .DEFAULT_GOAL := help
4 4
5 GO ?= go5 GO ?= go
6-PKG ?= ./cmd/zot6+PKG ?= ./cmd/zut
7-BINARY ?= zot7+BINARY ?= zut
8 BIN_DIR ?= bin8 BIN_DIR ?= bin
9 DEV_VERSION ?= 0.0.0-dev9 DEV_VERSION ?= 0.0.0-dev
10 VERSION ?= $(DEV_VERSION)10 VERSION ?= $(DEV_VERSION)
@@ -17,7 +17,7 @@ LDFLAGS := -s -w -X main.version=$(VERSION)
17 17
18 help:18 help:
19 @printf '%s\n' \19 @printf '%s\n' \
20- 'build build ./bin/zot' \20+ 'build build ./bin/zut' \
21 'install install the current checkout with go install' \21 'install install the current checkout with go install' \
22 'go-install install a published module version (GO_INSTALL_VERSION=latest)' \22 'go-install install a published module version (GO_INSTALL_VERSION=latest)' \
23 'test run all tests with the race detector' \23 'test run all tests with the race detector' \
@@ -27,7 +27,7 @@ help:
27 'lint-install install golangci-lint $(GOLANGCI_LINT_VERSION)' \27 'lint-install install golangci-lint $(GOLANGCI_LINT_VERSION)' \
28 'fmt format Go source files' \28 'fmt format Go source files' \
29 'check run test-fast and lint' \29 'check run test-fast and lint' \
30- 'run build and run ./bin/zot (pass ARGS="...")' \30+ 'run build and run ./bin/zut (pass ARGS="...")' \
31 'clean remove build output' \31 'clean remove build output' \
32 'release cross-build release binaries (set VERSION=...)'32 'release cross-build release binaries (set VERSION=...)'
33 33
@@ -41,7 +41,7 @@ install:
41 41
42 # Install a published/module version, rather than the current checkout.42 # Install a published/module version, rather than the current checkout.
43 go-install:43 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)
45 45
46 test:46 test:
47 $(GO) test -race ./...47 $(GO) test -race ./...
M README.md +191 -188
@@ -1,6 +1,6 @@
1 <div align="center">1 <div align="center">
2- <a href="https://www.zot.sh">2+ <a href="https://github.com/bnema/zut">
3- <img src="packages/provider/auth/assets/zot-logo.png" alt="zot coding agent harness" width="130" height="130" />3+ <img src="packages/provider/auth/assets/zut-logo.png" alt="zut coding agent harness" width="130" height="130" />
4 </a>4 </a>
5 </div>5 </div>
6 <br>6 <br>
@@ -10,9 +10,12 @@
10 <img alt="30+ providers" src="https://img.shields.io/badge/providers-30+-34E2EA">10 <img alt="30+ providers" src="https://img.shields.io/badge/providers-30+-34E2EA">
11 </p>11 </p>
12 <p align="center">12 <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>
14 </p>14 </p>
15 15
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+
16 ## What is it?19 ## What is it?
17 20
18 Yet another coding agent harness, lightweight and written (vibe-slopped) in go.21 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.
22 - five core built-in tools (read, write, edit, bash, and lsp); the conditional `skill` tool is available when skills are enabled.25 - five core built-in tools (read, write, edit, bash, and lsp); the conditional `skill` tool is available when skills are enabled.
23 - three run modes (interactive tui, print, json).26 - three run modes (interactive tui, print, json).
24 - built-in telegram bot.27 - 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).
26 - user and extension themes via JSON; see [docs/themes.md](docs/themes.md).29 - user and extension themes via JSON; see [docs/themes.md](docs/themes.md).
27 - standing instructions via `AGENTS.md` files (global and per-project); see [Persistent instructions](#persistent-instructions-agentsmd).30 - standing instructions via `AGENTS.md` files (global and per-project); see [Persistent instructions](#persistent-instructions-agentsmd).
28 - reusable instructions via `SKILL.md` files; see [docs/skills.md](docs/skills.md).31 - reusable instructions via `SKILL.md` files; see [docs/skills.md](docs/skills.md).
29 - named subagent profiles from `~/.agents/agents/*.md`; see [docs/subagents.md](docs/subagents.md).32 - 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).
31 34
32 ## Install35 ## Install
33 36
34 ### One-liner (macOS, Linux)37 ### One-liner (macOS, Linux)
35 38
36 ```bash39 ```bash
37-curl -fsSL https://www.zot.sh/install.sh | bash40+curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash
38 ```41 ```
39 42
40 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:43 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:
41 44
42 ```bash45 ```bash
43-curl -fsSL https://www.zot.sh/install.sh | bash -s -- v0.0.1 ~/bin46+curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash -s -- v0.1.0 ~/bin
44 ```47 ```
45 48
46 ### One-liner (Windows, PowerShell)49 ### One-liner (Windows, PowerShell)
47 50
48 ```powershell51 ```powershell
49-iwr -useb https://www.zot.sh/install.ps1 | iex52+iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex
50 ```53 ```
51 54
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.
53 56
54 ### go install57 ### go install
55 58
56 ```bash59 ```bash
57-go install github.com/patriceckhart/zot/cmd/zot@latest60+go install github.com/bnema/zut/cmd/zut@latest
58 ```61 ```
59 62
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`.
61 64
62 ### From source65 ### From source
63 66
64 ```bash67 ```bash
65-git clone https://github.com/patriceckhart/zot68+git clone https://github.com/bnema/zut
66-cd zot69+cd zut
67 make help # list the important developer commands70 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)
69 make install # install the current checkout with go install72 make install # install the current checkout with go install
70 make go-install # install the latest published module version73 make go-install # install the latest published module version
71 ```74 ```
@@ -76,23 +79,23 @@ development build.
76 79
77 ### Prebuilt binaries80 ### Prebuilt binaries
78 81
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`.
80 83
81 ## Authenticate84 ## Authenticate
82 85
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.
84 87
85 ### Credential lookup order88 ### Credential lookup order
86 89
87 1. `--api-key` flag90 1. `--api-key` flag
88 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)91 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)
90 93
91-`$ZOT_HOME` defaults to:94+`$ZUT_HOME` defaults to:
92-- All platforms: `$XDG_STATE_HOME/zot` when `XDG_STATE_HOME` is set95+- All platforms: `$XDG_STATE_HOME/zut` when `XDG_STATE_HOME` is set
93-- macOS fallback: `~/Library/Application Support/zot`96+- macOS fallback: `~/Library/Application Support/zut`
94-- Linux fallback: `~/.local/state/zot`97+- Linux fallback: `~/.local/state/zut`
95-- Windows fallback: `%LOCALAPPDATA%\zot`98+- Windows fallback: `%LOCALAPPDATA%\zut`
96 99
97 ### API keys from commands100 ### API keys from commands
98 101
@@ -112,37 +115,37 @@ To keep an API key in a password manager instead of `auth.json`, configure an `a
112 115
113 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.116 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.
114 117
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.
116 119
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.
118 121
119 ### `/login` flow122 ### `/login` flow
120 123
121-Run `zot` and type `/login`. Pick one of two methods:124+Run `zut` and type `/login`. Pick one of two methods:
122 125
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.
124 - **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.127 - **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.
125 - 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.128 - 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.
126 - Anthropic uses the Claude Code OAuth flow. Messages go to `api.anthropic.com` with a bearer token and the Claude Code identity headers.129 - Anthropic uses the Claude Code OAuth flow. Messages go to `api.anthropic.com` with a bearer token and the Claude Code identity headers.
127 - 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.130 - 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.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.
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.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.
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.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.
131 134
132 > **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.135 > **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.
133 136
134 ### Token refresh137 ### Token refresh
135 138
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:
137 140
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.
139 - The telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.142 - The telegram bridge additionally refreshes once per turn so a bot that runs for days keeps working without manual intervention.
140 - 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.143 - 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.
141 144
142-All data lives under `$ZOT_HOME`:145+All data lives under `$ZUT_HOME`:
143 146
144 ```147 ```
145-$ZOT_HOME/148+$ZUT_HOME/
146 ├── config.json # last-used provider/model/theme and persistent settings149 ├── config.json # last-used provider/model/theme and persistent settings
147 ├── auth.json # api keys and oauth tokens (mode 0600)150 ├── auth.json # api keys and oauth tokens (mode 0600)
148 ├── sessions/ # jsonl transcripts, one dir per cwd151 ├── sessions/ # jsonl transcripts, one dir per cwd
@@ -155,24 +158,24 @@ $ZOT_HOME/
155 └── logs/ # app log files158 └── logs/ # app log files
156 ```159 ```
157 160
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.
159 162
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.
161 164
162 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.165 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.
163 166
164 ## Persistent instructions (AGENTS.md)167 ## Persistent instructions (AGENTS.md)
165 168
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.
167 170
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:
169 172
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).
171 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.174 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.
172 175
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.
174 177
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:
176 179
177 ```markdown180 ```markdown
178 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.181 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
182 185
183 | Mechanism | Scope | Effect |186 | Mechanism | Scope | Effect |
184 |---|---|---|187 |---|---|---|
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 |
186 | `./AGENTS.md` (and parent dirs) | project | appended to the default prompt |189 | `./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 |
188 | `--append-system-prompt <text>` | single run | appends for one invocation (repeatable) |191 | `--append-system-prompt <text>` | single run | appends for one invocation (repeatable) |
189 | `--system-prompt <text>` | single run | replaces the built-in identity for one invocation; append addenda remain |192 | `--system-prompt <text>` | single run | replaces the built-in identity for one invocation; append addenda remain |
190 193
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.
192 195
193 ## Changelog on update196 ## Changelog on update
194 197
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.
196 199
197 ## Usage200 ## Usage
198 201
199 ```bash202 ```bash
200-zot # interactive tui203+zut # interactive tui
201-zot "fix the failing test" # tui, pre-filled prompt204+zut "fix the failing test" # tui, pre-filled prompt
202-zot -p "list all go files" # print final text, exit205+zut -p "list all go files" # print final text, exit
203-zot -p --stats stats.json "task" # print final text and write generation stats206+zut -p --stats stats.json "task" # print final text and write generation stats
204-zot --json "refactor main.go" # newline-delimited json events, exit207+zut --json "refactor main.go" # newline-delimited json events, exit
205-zot --continue # resume the most recent session for this cwd208+zut --continue # resume the most recent session for this cwd
206-zot --resume # pick a session to resume209+zut --resume # pick a session to resume
207-zot --list-models # show supported models210+zut --list-models # show supported models
208-zot --help211+zut --help
209 ```212 ```
210 213
211 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.214 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
219 | `--api-key <key>` | Override the API key. |222 | `--api-key <key>` | Override the API key. |
220 | `--base-url <url>` | Override the provider base URL (tests, self-hosted). |223 | `--base-url <url>` | Override the provider base URL (tests, self-hosted). |
221 | `--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. |224 | `--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`). |
223 | `--append-system-prompt <text>` | Append text to the system prompt (repeatable). |226 | `--append-system-prompt <text>` | Append text to the system prompt (repeatable). |
224 | `--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`. |227 | `--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`. |
225 | `--stats <path>` | With `-p`/`--print`, write generation statistics as JSON. |228 | `--stats <path>` | With `-p`/`--print`, write generation statistics as JSON. |
@@ -242,7 +245,7 @@ Print-mode stats contain `provider`, `model`, `prompt_tokens`, `reasoning_tokens
242 - `read`: read text files, or inline images (PNG, JPEG, GIF, WebP).245 - `read`: read text files, or inline images (PNG, JPEG, GIF, WebP).
243 - `write`: create or overwrite files, making parent directories as needed.246 - `write`: create or overwrite files, making parent directories as needed.
244 - `edit`: one or more exact-match replacements in an existing file.247 - `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.
246 - `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).249 - `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).
247 250
248 When the sandbox is on (see `/jail`), filesystem tools and LSP workspace edits refuse paths outside the session cwd.251 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
250 ## Modes253 ## Modes
251 254
252 - **Interactive** (default): chat TUI with streaming output, spinner, cost meter, slash commands.255 - **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.256+- **Print**: `zut -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.257+- **Stream**: `zut --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).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).
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.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.
257 260
258-## zotfile agents261+## zutfile agents
259 262
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:
261 264
262 ```bash265 ```bash
263-zot run ./my-agent266+zut run ./my-agent
264-zot run ./my-agent.zot267+zut run ./my-agent.zut
265-zot run frkr/zot-archify268+zut run frkr/zot-archify
266-zot run acme/agents/code-reviewer --cwd /path/to/project269+zut run acme/agents/code-reviewer --cwd /path/to/project
267 ```270 ```
268 271
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.
270 273
271 ## Embedding274 ## Embedding
272 275
273-Two ways to drive zot from another program:276+Two ways to drive zut from another program:
274 277
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/`.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/`.
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/`.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/`.
277 280
278 Both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.281 Both interfaces share the same event schema, so transcripts captured by one can be replayed through the other.
279 282
@@ -285,26 +288,26 @@ Slash command names are case-insensitive in the TUI and messaging backends; argu
285 |---|---|288 |---|---|
286 | `/help` | Show key bindings and commands. |289 | `/help` | Show key bindings and commands. |
287 | `/login` | Log in via API key or subscription (opens a dialog). |290 | `/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. |
289 | `/model` | Pick a model from a list (or `/model <id>` to set directly). |292 | `/model` | Pick a model from a list (or `/model <id>` to set directly). |
290 | `/reasoning` | Set the reasoning level for subsequent model calls. |293 | `/reasoning` | Set the reasoning level for subsequent model calls. |
291 | `/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. |294 | `/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. |
292 | `/sessions` | Resume a previous session for this directory. The picker omits branches created only for tree navigation. |295 | `/sessions` | Resume a previous session for this directory. The picker omits branches created only for tree navigation. |
293 | `/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. |296 | `/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. |
295 | `/jump` | Scroll the chat to a previous turn (or `/jump <text>` to filter). |298 | `/jump` | Scroll the chat to a previous turn (or `/jump <text>` to filter). |
296 | `/btw` | Side chat with full context that doesn't add to the main thread. |299 | `/btw` | Side chat with full context that doesn't add to the main thread. |
297 | `/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). |300 | `/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). |
298 | `/skills` | List discovered skills (SKILL.md files) and preview their bodies. |301 | `/skills` | List discovered skills (SKILL.md files) and preview their bodies. |
299 | `/compact` | Summarize the transcript into one message to free up context. |302 | `/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`. |
301 | `/jail` | Confine tools to the current directory. |304 | `/jail` | Confine tools to the current directory. |
302 | `/unjail` | Allow tools to touch paths outside again. |305 | `/unjail` | Allow tools to touch paths outside again. |
303 | `/reload-ext` | Hot-reload all extensions (re-read manifests, respawn subprocesses, rebuild tool registry). |306 | `/reload-ext` | Hot-reload all extensions (re-read manifests, respawn subprocesses, rebuild tool registry). |
304 | `/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`. |307 | `/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. |
306 | `/clear` | Clear the chat transcript. |309 | `/clear` | Clear the chat transcript. |
307-| `/exit` | Exit zot. |310+| `/exit` | Exit zut. |
308 311
309 Extension-registered commands appear under a divider at the bottom of the popup, sorted by name.312 Extension-registered commands appear under a divider at the bottom of the popup, sorted by name.
310 313
@@ -314,17 +317,17 @@ Type `!` followed by a command to run it directly without going through the mode
314 317
315 ### `/sessions`318 ### `/sessions`
316 319
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.
318 321
319 ### `/session`322 ### `/session`
320 323
321 Four ops on the current session. `/session` alone opens a picker; each is also runnable directly.324 Four ops on the current session. `/session` alone opens a picker; each is also runnable directly.
322 325
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.
324 327
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.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.
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).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).
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.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.
328 - **`/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.331 - **`/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.
329 332
330 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 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
333 336
334 ### `/jump`337 ### `/jump`
335 338
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.
337 340
338 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.341 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.
339 342
@@ -352,7 +355,7 @@ Inside the overlay: `enter` sends, `esc` cancels an in-flight call (or closes th
352 355
353 ### `/subagents`356 ### `/subagents`
354 357
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.
356 359
357 > **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.360 > **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.
358 361
@@ -389,37 +392,37 @@ Background subagents that run alongside your main session. Each one is a separat
389 392
390 **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.393 **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.
391 394
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.
393 396
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.
395 398
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.
397 400
398 **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`.401 **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`.
399 402
400 ### Named subagent profiles403 ### Named subagent profiles
401 404
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.
403 406
404 ### `/settings`407 ### `/settings`
405 408
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:
407 410
408 - **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.411 - **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.
409 - **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.412 - **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`.
411 - **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.414 - **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.
413 - **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.416 - **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.
414 - **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`.417 - **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`.
415 - **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.418 - **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.
417 - **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.420 - **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.
418 - **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`.421 - **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`.
420 - **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).423 - **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).
421 - **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`.424 - **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).
423 - **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.426 - **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.
424 427
425 ### `/skills`428 ### `/skills`
@@ -430,21 +433,21 @@ Opens a picker listing every discovered SKILL.md file, built-ins hidden. Each ro
430 433
431 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%.434 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%.
432 435
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.
434 437
435 ### `/jail`438 ### `/jail`
436 439
437 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.440 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.
438 441
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.
440 443
441 ## Sessions444 ## Sessions
442 445
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.
444 447
445 ## Providers448 ## Providers
446 449
447-zot's built-in provider catalog includes:450+zut's built-in provider catalog includes:
448 451
449 - **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`).452 - **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`).
450 - **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.453 - **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
457 460
458 `--list-models` or the `/model` picker shows the full catalog across all built-in providers. Three sources:461 `--list-models` or the `/model` picker shows the full catalog across all built-in providers. Three sources:
459 462
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.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.
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).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).
462 - **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.465 - **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.
463 466
464 The context meter in the status line uses the model's advertised context window to show how much of it your last turn consumed.467 The context meter in the status line uses the model's advertised context window to show how much of it your last turn consumed.
465 468
466 ### Model fallback (rescue)469 ### Model fallback (rescue)
467 470
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.
469 472
470 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.473 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.
471 474
@@ -477,7 +480,7 @@ No configuration is required — the candidate list is built dynamically from yo
477 480
478 ### Custom models481 ### Custom models
479 482
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:
481 484
482 ```json485 ```json
483 {486 {
@@ -526,7 +529,7 @@ A top-level provider key that is not a built-in id defines a custom provider. Gi
526 529
527 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.530 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.
528 531
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`:
530 533
531 ```json534 ```json
532 {535 {
@@ -545,15 +548,15 @@ To retrieve this custom provider's key from a password manager, add a matching e
545 The provider IDs in `models.json` and `auth.json` must match. Then select the custom model directly:548 The provider IDs in `models.json` and `auth.json` must match. Then select the custom model directly:
546 549
547 ```bash550 ```bash
548-zot --provider my-company --model company-llm-v2551+zut --provider my-company --model company-llm-v2
549 ```552 ```
550 553
551 ### Kimi Code554 ### Kimi Code
552 555
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.
554 557
555 ```bash558 ```bash
556-zot --provider kimi559+zut --provider kimi
557 ```560 ```
558 561
559 By default this uses:562 By default this uses:
@@ -566,15 +569,15 @@ Credential lookup order for Kimi:
566 1. `--api-key`569 1. `--api-key`
567 2. `KIMI_API_KEY`570 2. `KIMI_API_KEY`
568 3. `MOONSHOT_API_KEY`571 3. `MOONSHOT_API_KEY`
569-4. `$ZOT_HOME/auth.json`572+4. `$ZUT_HOME/auth.json`
570 5. the official Kimi Code CLI token at `~/.kimi/credentials/kimi-code.json`, unless disabled by `/logout kimi`573 5. the official Kimi Code CLI token at `~/.kimi/credentials/kimi-code.json`, unless disabled by `/logout kimi`
571 574
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.
573 576
574 For direct Moonshot API keys or a custom compatible endpoint:577 For direct Moonshot API keys or a custom compatible endpoint:
575 578
576 ```bash579 ```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"
578 ```581 ```
579 582
580 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.583 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
587 590
588 ### DeepSeek591 ### DeepSeek
589 592
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.
591 594
592 ```bash595 ```bash
593-zot --provider deepseek596+zut --provider deepseek
594 ```597 ```
595 598
596 By default this uses:599 By default this uses:
@@ -604,26 +607,26 @@ Credential lookup order for DeepSeek:
604 607
605 1. `--api-key`608 1. `--api-key`
606 2. `DEEPSEEK_API_KEY`609 2. `DEEPSEEK_API_KEY`
607-3. `$ZOT_HOME/auth.json`610+3. `$ZUT_HOME/auth.json`
608 611
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`.
610 613
611 > **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`.614 > **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`.
612 615
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.
614 617
615 For a custom-compatible endpoint (mirror, gateway, self-host):618 For a custom-compatible endpoint (mirror, gateway, self-host):
616 619
617 ```bash620 ```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"
619 ```622 ```
620 623
621 ### Google Gemini624 ### Google Gemini
622 625
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/).
624 627
625 ```bash628 ```bash
626-zot --provider google629+zut --provider google
627 ```630 ```
628 631
629 By default this uses:632 By default this uses:
@@ -638,9 +641,9 @@ Credential lookup order for Google:
638 1. `--api-key`641 1. `--api-key`
639 2. `GEMINI_API_KEY`642 2. `GEMINI_API_KEY`
640 3. `GOOGLE_API_KEY`643 3. `GOOGLE_API_KEY`
641-4. `$ZOT_HOME/auth.json`644+4. `$ZUT_HOME/auth.json`
642 645
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`.
644 647
645 > **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.648 > **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.
646 649
@@ -652,10 +655,10 @@ You can add additional Gemini model IDs to `models.json` under the `google` prov
652 655
653 ### Gemini Enterprise Agent Platform (formerly Google Vertex AI)656 ### Gemini Enterprise Agent Platform (formerly Google Vertex AI)
654 657
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.
656 659
657 ```bash660 ```bash
658-zot --provider google-vertex661+zut --provider google-vertex
659 ```662 ```
660 663
661 Required configuration (env vars, read at construction time):664 Required configuration (env vars, read at construction time):
@@ -667,34 +670,34 @@ Credential lookup order for Vertex:
667 670
668 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.671 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.
669 2. `GOOGLE_APPLICATION_CREDENTIALS`: path to a supported credential JSON file.672 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.
671 674
672 Two credential file shapes are supported:675 Two credential file shapes are supported:
673 676
674-- `type: "service_account"`: zot signs a JWT with the private key and exchanges it 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.
675-- `type: "authorized_user"`: zot exchanges the stored client ID, client secret, and refresh token 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.
676 679
677 Access tokens are cached in memory and refreshed on demand.680 Access tokens are cached in memory and refreshed on demand.
678 681
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`.
680 683
681 ### Local models with ollama684 ### Local models with ollama
682 685
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.
684 687
685 Quick start:688 Quick start:
686 689
687 ```bash690 ```bash
688 ollama pull qwen3.5:4b691 ollama pull qwen3.5:4b
689-zot --provider ollama --model qwen3.5:4b692+zut --provider ollama --model qwen3.5:4b
690 ```693 ```
691 694
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`.
693 696
694 For a remote ollama instance or one behind auth:697 For a remote ollama instance or one behind auth:
695 698
696 ```bash699 ```bash
697-zot --provider ollama --model llama3 --base-url https://my-server.com/v1 --api-key my-token700+zut --provider ollama --model llama3 --base-url https://my-server.com/v1 --api-key my-token
698 ```701 ```
699 702
700 You can also add models to your `models.json` so you don't need flags every time:703 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
720 723
721 ### Local models with llama.cpp router mode724 ### Local models with llama.cpp router mode
722 725
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.
724 727
725 Install or update llama.cpp. On macOS with Homebrew:728 Install or update llama.cpp. On macOS with Homebrew:
726 729
@@ -730,7 +733,7 @@ brew install llama.cpp
730 brew upgrade llama.cpp733 brew upgrade llama.cpp
731 ```734 ```
732 735
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.
734 737
735 ```bash738 ```bash
736 mkdir -p ~/llama-models739 mkdir -p ~/llama-models
@@ -747,7 +750,7 @@ llama-server \
747 750
748 `--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.751 `--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.
749 752
750-Confirm that router mode is active before configuring zot:753+Confirm that router mode is active before configuring zut:
751 754
752 ```bash755 ```bash
753 curl http://127.0.0.1:8080/health756 curl http://127.0.0.1:8080/health
@@ -756,7 +759,7 @@ curl http://127.0.0.1:8080/models
756 759
757 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.760 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.
758 761
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`.
760 763
761 You can configure the same connection through environment variables:764 You can configure the same connection through environment variables:
762 765
@@ -775,7 +778,7 @@ Run `/llama` to:
775 - explicitly load or unload a model778 - explicitly load or unload a model
776 - press `d` to ask the router to remove a downloaded cache model after confirmation779 - press `d` to ask the router to remove a downloaded cache model after confirmation
777 780
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.
779 782
780 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`.783 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`.
781 784
@@ -787,7 +790,7 @@ In the main chat, `ctrl+v` checks the system clipboard for an image before falli
787 790
788 ## Inline images791 ## Inline images
789 792
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:
791 794
792 | Value | Effect |795 | Value | Effect |
793 |---|---|796 |---|---|
@@ -796,15 +799,15 @@ When a tool returns an image (for example `read` on a PNG), zot renders it inlin
796 | `kitty` | Force the Kitty graphics protocol. |799 | `kitty` | Force the Kitty graphics protocol. |
797 | `off`, `none` | Always use the text placeholder. |800 | `off`, `none` | Always use the text placeholder. |
798 801
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.
800 803
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.
802 805
803 ## Tool rendering806 ## Tool rendering
804 807
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.
806 809
807-Set the `tool_render` key in `$ZOT_HOME/config.json`:810+Set the `tool_render` key in `$ZUT_HOME/config.json`:
808 811
809 ```json812 ```json
810 {813 {
@@ -817,7 +820,7 @@ Set the `tool_render` key in `$ZOT_HOME/config.json`:
817 | unset / `"box"` (default) | Each tool call is wrapped in a bordered panel. |820 | unset / `"box"` (default) | Each tool call is wrapped in a bordered panel. |
818 | `"flat"` | Boxless: a quiet header line plus indented output. |821 | `"flat"` | Boxless: a quiet header line plus indented output. |
819 822
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:
821 824
822 | Value | Effect |825 | Value | Effect |
823 |---|---|826 |---|---|
@@ -826,18 +829,18 @@ The `ZOT_FLAT_TOOLS` env var overrides the config for a single run, which is han
826 | unset | Fall back to the `tool_render` config key. |829 | unset | Fall back to the `tool_render` config key. |
827 830
828 ```sh831 ```sh
829-ZOT_FLAT_TOOLS=1 zot # flat, just this run832+ZUT_FLAT_TOOLS=1 zut # flat, just this run
830-ZOT_FLAT_TOOLS=0 zot # boxes, even if config.json says "flat"833+ZUT_FLAT_TOOLS=0 zut # boxes, even if config.json says "flat"
831 ```834 ```
832 835
833 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.836 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.
834 837
835 ### Tool arg width838 ### Tool arg width
836 839
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:
838 841
839 ```sh842 ```sh
840-ZOT_TOOL_ARG_WIDTH=120 zot # allow up to 120 cells before truncating843+ZUT_TOOL_ARG_WIDTH=120 zut # allow up to 120 cells before truncating
841 ```844 ```
842 845
843 | Value | Effect |846 | Value | Effect |
@@ -861,17 +864,17 @@ By default a message you send renders as a padded, background-tinted bubble: a b
861 | unset / `false` (default) | Padded, background-tinted user bubble. |864 | unset / `false` (default) | Padded, background-tinted user bubble. |
862 | `true` | One quiet gutter line per wrapped row. |865 | `true` | One quiet gutter line per wrapped row. |
863 866
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):
865 868
866 ```sh869 ```sh
867-ZOT_COMPACT_INPUT=1 zot # compact, just this run870+ZUT_COMPACT_INPUT=1 zut # compact, just this run
868 ```871 ```
869 872
870 ## Queued messages873 ## Queued messages
871 874
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.
873 876
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`.
875 878
876 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.879 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.
877 880
@@ -927,49 +930,49 @@ Type `@` followed by a filter string to narrow the list (e.g. `@read` shows only
927 930
928 ## Extensions931 ## Extensions
929 932
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`.
931 934
932 ### Install and manage935 ### Install and manage
933 936
934 ```bash937 ```bash
935-zot ext install <path|git-url> # copy / clone into $ZOT_HOME/extensions/938+zut ext install <path|git-url> # copy / clone into $ZUT_HOME/extensions/
936-zot ext install --build=go <path> # explicitly build and install local Go source939+zut ext install --build=go <path> # explicitly build and install local Go source
937-zot ext list # show installed extensions940+zut ext list # show installed extensions
938-zot ext doctor # diagnose load, registration, and conflict issues941+zut ext doctor # diagnose load, registration, and conflict issues
939-zot ext logs <name> [-f] # cat or tail the extension's stderr log942+zut ext logs <name> [-f] # cat or tail the extension's stderr log
940-zot ext enable <name> # re-enable a disabled extension943+zut ext enable <name> # re-enable a disabled extension
941-zot ext disable <name> # disable without removing944+zut ext disable <name> # disable without removing
942-zot ext remove <name> # delete an extension directory945+zut ext remove <name> # delete an extension directory
943 ```946 ```
944 947
945-For local installs, zot validates `extension.json` and any executable path948+For local installs, zut validates `extension.json` and any executable path
946 relative to the extension directory before reporting success. It never runs a949 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 a950+build implicitly. Use `zut ext install --build=go <path>` to explicitly build a
948 local Go extension; other source-based extensions must provide their runtime951 local Go extension; other source-based extensions must provide their runtime
949 artifact or launcher themselves. A failed validation or build leaves no952 artifact or launcher themselves. A failed validation or build leaves no
950 partial installation behind.953 partial installation behind.
951 954
952-`zot ext doctor` keeps normal extension startup fail-soft, but gives955+`zut ext doctor` keeps normal extension startup fail-soft, but gives
953 you an explicit troubleshooting view: manifest errors, disabled or956 you an explicit troubleshooting view: manifest errors, disabled or
954 shadowed extensions, subprocess load errors, ready/auto-ready status,957 shadowed extensions, subprocess load errors, ready/auto-ready status,
955 registered commands/tools, registration conflicts, warnings, and the958 registered commands/tools, registration conflicts, warnings, and the
956 stderr log path.959 stderr log path.
957 960
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.
959 962
960 ### Updating extensions963 ### Updating extensions
961 964
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:
963 966
964 - Disabled extensions are skipped.967 - 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.968+- Extensions without a `.git/` directory (installed by `zut 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.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.
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.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.
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.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.
969 972
970 ### Theme-only extensions973 ### Theme-only extensions
971 974
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).
973 976
974 ### Reference977 ### Reference
975 978
@@ -977,12 +980,12 @@ An extension may ship only a theme: `extension.json` plus `theme.json` (or `them
977 980
978 ## Skills981 ## Skills
979 982
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.
981 984
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:
983 986
984-- `./.zot/skills/<name>/SKILL.md` (project)987+- `./.zut/skills/<name>/SKILL.md` (project)
985-- `$ZOT_HOME/skills/<name>/SKILL.md` (global)988+- `$ZUT_HOME/skills/<name>/SKILL.md` (global)
986 - `./.claude/skills/<name>/SKILL.md`, `~/.claude/skills/<name>/SKILL.md` (Claude-compatible layout)989 - `./.claude/skills/<name>/SKILL.md`, `~/.claude/skills/<name>/SKILL.md` (Claude-compatible layout)
987 - `./.agents/skills/<name>/SKILL.md`, `~/.agents/skills/<name>/SKILL.md` (agent-compatible layout)990 - `./.agents/skills/<name>/SKILL.md`, `~/.agents/skills/<name>/SKILL.md` (agent-compatible layout)
988 991
@@ -992,14 +995,14 @@ See [docs/skills.md](docs/skills.md) for the frontmatter fields, authoring tips,
992 995
993 ## Telegram bot (bridge)996 ## Telegram bot (bridge)
994 997
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).
996 999
997 ### From inside the TUI1000 ### From inside the TUI
998 1001
999 Type `/telegram` in the running TUI to open a picker with **connect**, **disconnect**, and **status**. When connected:1002 Type `/telegram` in the running TUI to open a picker with **connect**, **disconnect**, and **status**. When connected:
1000 1003
1001 - 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.1004 - 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.
1003 - 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).1006 - 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).
1004 - The status bar shows a `- tg -` tag while the bridge is active.1007 - The status bar shows a `- tg -` tag while the bridge is active.
1005 - `/telegram connect` / `/telegram disconnect` / `/telegram status` (or `/tg`) also work as direct commands without the picker.1008 - `/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
1011 For headless servers or long-running bots unattached to a TUI:1014 For headless servers or long-running bots unattached to a TUI:
1012 1015
1013 ```bash1016 ```bash
1014-zot telegram-bot setup # paste a BotFather token, verify, save1017+zut telegram-bot setup # paste a BotFather token, verify, save
1015-zot telegram-bot run # foreground: long-poll in this terminal (ctrl+c to stop)1018+zut telegram-bot run # foreground: long-poll in this terminal (ctrl+c to stop)
1016-zot telegram-bot start # background: detach and return immediately1019+zut telegram-bot start # background: detach and return immediately
1017-zot telegram-bot stop # SIGTERM the background bot (SIGKILL after 5s)1020+zut 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)1021+zut telegram-bot logs -f # tail $ZUT_HOME/logs/bot.log (omit -f to just cat)
1019-zot telegram-bot status # config (token masked) + running/stopped1022+zut telegram-bot status # config (token masked) + running/stopped
1020-zot telegram-bot reset # forget the token and paired user1023+zut telegram-bot reset # forget the token and paired user
1021-# short alias: `zot tg ...` is accepted for every subcommand1024+# short alias: `zut tg ...` is accepted for every subcommand
1022 ```1025 ```
1023 1026
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.
1025 1028
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.
1027 1030
1028 Setup flow:1031 Setup flow:
1029 1032
1030 1. Talk to [@BotFather](https://t.me/BotFather) on telegram, run `/newbot`, copy the token it gives you.1033 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.1034+2. Run `zut 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.1035+3. Run `zut telegram-bot run` in the directory you want the agent to operate in.
1033 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.1036 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.
1034 1037
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).
1036 1039
1037 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.1040 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.
1038 1041
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.
1040 1043
1041 ### Architecture: protocol-agnostic bot core1044 ### Architecture: protocol-agnostic bot core
1042 1045
@@ -1048,7 +1051,7 @@ This means additional messaging backends (Discord, Slack, Signal, and similar) c
1048 1051
1049 ```bash1052 ```bash
1050 make help # list the important developer commands1053 make help # list the important developer commands
1051-make build # build ./bin/zot as 0.0.0-dev1054+make build # build ./bin/zut as 0.0.0-dev
1052 make test # go test -race ./...1055 make test # go test -race ./...
1053 make test-fast # go test ./...1056 make test-fast # go test ./...
1054 make lint # golangci-lint + go vet + gofmt check1057 make lint # golangci-lint + go vet + gofmt check
@@ -1062,7 +1065,7 @@ make release VERSION=0.1.0 # cross-compile release binaries
1062 Source layout (single Go module, four packages under `packages/`):1065 Source layout (single Go module, four packages under `packages/`):
1063 1066
1064 ```1067 ```
1065-cmd/zot/ main()1068+cmd/zut/ main()
1066 packages/provider/ LLM client surface, model catalog, streaming clients1069 packages/provider/ LLM client surface, model catalog, streaming clients
1067 packages/provider/auth/ credential store, api-key probe, oauth, login server1070 packages/provider/auth/ credential store, api-key probe, oauth, login server
1068 packages/core/ agent loop, sessions, cost tracking, compaction1071 packages/core/ agent loop, sessions, cost tracking, compaction
@@ -1077,12 +1080,12 @@ packages/agent/modes/telegram/ telegram adapter, api client, daemon
1077 packages/agent/tools/ read, write, edit, bash, lsp, sandbox1080 packages/agent/tools/ read, write, edit, bash, lsp, sandbox
1078 packages/agent/skills/ skill discovery, frontmatter parser, skill tool1081 packages/agent/skills/ skill discovery, frontmatter parser, skill tool
1079 packages/agent/subagents/ named profiles, supervisor, and background runtime1082 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)
1081 packages/agent/ext/ public Go SDK for writing extensions (package ext)1084 packages/agent/ext/ public Go SDK for writing extensions (package ext)
1082 ```1085 ```
1083 1086
1084 Downstream consumers can depend on individual packages:1087 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.
1086 1089
1087 ## License1090 ## License
1088 1091
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.
2 package main2 package main
3 3
4 import (4 import (
@@ -8,12 +8,12 @@ import (
8 "runtime/debug"8 "runtime/debug"
9 "strings"9 "strings"
10 10
11- "github.com/patriceckhart/zot/packages/agent"11+ "github.com/bnema/zut/packages/agent"
12 )12 )
13 13
14 // Injected at build time via -ldflags "-X main.version=... -X main.commit=... -X main.date=...".14 // Injected at build time via -ldflags "-X main.version=... -X main.commit=... -X main.date=...".
15 // See .goreleaser.yaml for the release build and the Makefile for15 // See .goreleaser.yaml for the release build and the Makefile for
16-// local builds. Defaults make `zot --version` print something sensible16+// local builds. Defaults make `zut --version` print something sensible
17 // when built without ldflags.17 // when built without ldflags.
18 var (18 var (
19 // 0.0.0-dev is the placeholder for local / untagged builds.19 // 0.0.0-dev is the placeholder for local / untagged builds.
@@ -37,12 +37,12 @@ func main() {
37 v = v + ")"37 v = v + ")"
38 }38 }
39 if err := agent.Run(os.Args[1:], v); err != nil {39 if err := agent.Run(os.Args[1:], v); err != nil {
40- fmt.Fprintln(os.Stderr, "zot:", err)40+ fmt.Fprintln(os.Stderr, "zut:", err)
41 os.Exit(1)41 os.Exit(1)
42 }42 }
43 }43 }
44 44
45-// resolvedVersion falls back to the module version embedded by Go when zot is45+// resolvedVersion falls back to the module version embedded by Go when zut is
46 // installed with "go install ...@version". Release archives still use the46 // installed with "go install ...@version". Release archives still use the
47 // version injected by GoReleaser, and local source builds remain 0.0.0-dev.47 // version injected by GoReleaser, and local source builds remain 0.0.0-dev.
48 func resolvedVersion(linkedVersion string) string {48 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 (
9 "sort"9 "sort"
10 )10 )
11 11
12-// embeddedDocs holds zot's user-facing documentation so installed12+// embeddedDocs holds zut's user-facing documentation so installed
13 // binaries do not depend on a source checkout existing on disk.13 // binaries do not depend on a source checkout existing on disk.
14 //14 //
15 //go:embed README.md docs/*.md15 //go:embed README.md docs/*.md
@@ -23,11 +23,11 @@ var docFiles = map[string]string{
23 "themes.md": "docs/themes.md",23 "themes.md": "docs/themes.md",
24 }24 }
25 25
26-// EnsureInstalled writes the embedded docs into $ZOT_HOME/docs and26+// EnsureInstalled writes the embedded docs into $ZUT_HOME/docs and
27 // returns that directory. Existing files are left untouched when their27 // returns that directory. Existing files are left untouched when their
28 // content already matches the embedded copy.28 // content already matches the embedded copy.
29-func EnsureInstalled(zotHome string) (string, error) {29+func EnsureInstalled(zutHome string) (string, error) {
30- dir := filepath.Join(zotHome, "docs")30+ dir := filepath.Join(zutHome, "docs")
31 if err := os.MkdirAll(dir, 0o755); err != nil {31 if err := os.MkdirAll(dir, 0o755); err != nil {
32 return dir, err32 return dir, err
33 }33 }
M docs/extensions.md +84 -80
@@ -1,6 +1,6 @@
1-# zot extensions1+# zut extensions
2 2
3-zot can be extended with custom slash commands by running an external3+zut can be extended with custom slash commands by running an external
4 program as a subprocess and exchanging newline-delimited JSON over4 program as a subprocess and exchanging newline-delimited JSON over
5 its stdin/stdout. Extensions can be written in **any language** that5 its stdin/stdout. Extensions can be written in **any language** that
6 can read and write JSON lines from stdio — Go, TypeScript, Python,6 can read and write JSON lines from stdio — Go, TypeScript, Python,
@@ -12,7 +12,7 @@ Six phases shipped so far:
12 - **Phase 2**: tools the LLM can call.12 - **Phase 2**: tools the LLM can call.
13 - **Phase 3**: lifecycle event subscriptions + tool-call interception13 - **Phase 3**: lifecycle event subscriptions + tool-call interception
14 for guardrail extensions.14 for guardrail extensions.
15-- **Phase 4**: interactive extension-owned panels rendered inside zot.15+- **Phase 4**: interactive extension-owned panels rendered inside zut.
16 - **Phase 5**: branch-aware session lifecycle events and opaque extension16 - **Phase 5**: branch-aware session lifecycle events and opaque extension
17 state persisted with session files.17 state persisted with session files.
18 - **Phase 6**: hidden per-turn context, persistent status/widgets, and18 - **Phase 6**: hidden per-turn context, persistent status/widgets, and
@@ -28,18 +28,20 @@ no SDK required:
28 28
29 ```python29 ```python
30 #!/usr/bin/env python330 #!/usr/bin/env python3
31-# $ZOT_HOME/extensions/hello-py/hello.py31+# $ZUT_HOME/extensions/hello-py/hello.py
32 import json, sys32 import json, sys
33 33
34 def emit(obj):34 def emit(obj):
35 sys.stdout.write(json.dumps(obj) + "\n")35 sys.stdout.write(json.dumps(obj) + "\n")
36 sys.stdout.flush()36 sys.stdout.flush()
37 37
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"]})
39 39
40 for line in sys.stdin:40 for line in sys.stdin:
41 msg = json.loads(line)41 msg = json.loads(line)
42 if msg["type"] == "hello_ack":42 if msg["type"] == "hello_ack":
43+ if msg.get("protocol_version") != 2:
44+ raise SystemExit("unsupported extension protocol version")
43 emit({"type":"register_command","name":"hellopy","description":"say hi (python)"})45 emit({"type":"register_command","name":"hellopy","description":"say hi (python)"})
44 emit({"type":"ready"})46 emit({"type":"ready"})
45 elif msg["type"] == "command_invoked":47 elif msg["type"] == "command_invoked":
@@ -63,58 +65,58 @@ Drop it in a directory with this `extension.json`:
63 ```65 ```
64 66
65 `exec` is required for protocol extensions. If an extension only ships67 `exec` is required for protocol extensions. If an extension only ships
66-`theme.json` or `themes/theme.json`, no `exec` is required and zot does68+`theme.json` or `themes/theme.json`, no `exec` is required and zut does
67 not spawn a subprocess.69 not spawn a subprocess.
68 70
69 `chmod +x hello.py`, install:71 `chmod +x hello.py`, install:
70 72
71 ```bash73 ```bash
72-zot ext install ./hello-py74+zut ext install ./hello-py
73 ```75 ```
74 76
75-Restart `zot`, type `/hellopy`, the agent greets you. Done.77+Restart `zut`, type `/hellopy`, the agent greets you. Done.
76 78
77 ## Built-in extensions79 ## Built-in extensions
78 80
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.
80 82
81 The `examples/extensions/` directory in the repo is reference code, not a default install set. To use any of those:83 The `examples/extensions/` directory in the repo is reference code, not a default install set. To use any of those:
82 84
83 ```bash85 ```bash
84 # install a Go example and explicitly build it in the staged install86 # install a Go example and explicitly build it in the staged install
85-zot ext install --build=go path/to/zot/examples/extensions/hello87+zut ext install --build=go path/to/zut/examples/extensions/hello
86 88
87 # or build it in the source tree for a one-run development load89 # or build it in the source tree for a one-run development load
88-cd path/to/zot/examples/extensions/hello90+cd path/to/zut/examples/extensions/hello
89 go build -o hello .91 go build -o hello .
90-zot --ext .92+zut --ext .
91 ```93 ```
92 94
93-`zot ext install` never builds source code implicitly. For local installs it95+`zut ext install` never builds source code implicitly. For local installs it
94 validates `extension.json` and any executable path relative to the extension96 validates `extension.json` and any executable path relative to the extension
95 directory, then stages the copy so a missing or ignored runtime artifact fails97 directory, then stages the copy so a missing or ignored runtime artifact fails
96 with an explicit error instead of becoming a broken installed extension. For a98 with an explicit error instead of becoming a broken installed extension. For a
97 local Go extension, opt into the fixed builder explicitly with99 local Go extension, opt into the fixed builder explicitly with
98-`zot ext install --build=go <path>`. The builder runs `go build -trimpath` from100+`zut ext install --build=go <path>`. The builder runs `go build -trimpath` from
99 the source directory, writes the manifest-declared relative executable into the101 the source directory, writes the manifest-declared relative executable into the
100 staging directory, and validates it before installation. It may resolve Go102 staging directory, and validates it before installation. It may resolve Go
101 modules and access the network according to the user's Go environment. Bare103 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 zot104+launchers such as `go`, `node`, and `npx` remain resolved from `PATH` when zut
103 starts; they cannot be used as the output of `--build=go`.105 starts; they cannot be used as the output of `--build=go`.
104 106
105 Nothing is auto-installed and nothing reaches out to the network without your explicit action.107 Nothing is auto-installed and nothing reaches out to the network without your explicit action.
106 108
107 ## Layout & discovery109 ## Layout & discovery
108 110
109-zot scans two directories on startup, in this order:111+zut scans two directories on startup, in this order:
110 112
111-1. **Project-local**: `./.zot/extensions/<name>/extension.json`113+1. **Project-local**: `./.zut/extensions/<name>/extension.json`
112-2. **Global**: `$ZOT_HOME/extensions/<name>/extension.json`114+2. **Global**: `$ZUT_HOME/extensions/<name>/extension.json`
113 115
114 A project-local extension with the same name wins over a global one.116 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 to117+When `XDG_STATE_HOME` is set on any platform, `$ZUT_HOME` defaults to
116-`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`118+`$XDG_STATE_HOME/zut`. Otherwise it defaults to `~/Library/Application Support/zut/`
117-on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.119+on macOS, `~/.local/state/zut` on Linux, or `%LOCALAPPDATA%\zut` on Windows.
118 120
119 Because each extension owns its own directory, the recommended place121 Because each extension owns its own directory, the recommended place
120 for extension state is inside that directory itself (for example122 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
123 `extension_dir` / `data_dir` so runtime code does not need to guess it.125 `extension_dir` / `data_dir` so runtime code does not need to guess it.
124 126
125 Each extension owns its own subdirectory. The `extension.json`127 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:
127 129
128 ```json130 ```json
129 {131 {
@@ -140,31 +142,32 @@ manifest tells zot how to launch it:
140 142
141 | field | meaning |143 | field | meaning |
142 |---|---|144 |---|---|
143-| `name` | required. how zot identifies the extension; must match what's sent in the `hello` frame. |145+| `name` | required. how zut identifies the extension; must match what's sent in the `hello` frame. |
144-| `version` | optional. shown in `zot ext list`. |146+| `version` | optional. shown in `zut ext list`. |
145 | `exec` | required. path to the executable (relative to the manifest). |147 | `exec` | required. path to the executable (relative to the manifest). |
146 | `args` | optional. extra argv passed to `exec`. |148 | `args` | optional. extra argv passed to `exec`. |
147 | `language` | optional. informational only (`go`, `python`, `typescript`, ...). |149 | `language` | optional. informational only (`go`, `python`, `typescript`, ...). |
148-| `description` | optional. shown in `zot ext list`. |150+| `description` | optional. shown in `zut ext list`. |
149 | `skills` | optional. relative directories containing bundled `<name>/SKILL.md` files or a direct `SKILL.md`. Paths must remain inside the extension directory. |151 | `skills` | optional. relative directories containing bundled `<name>/SKILL.md` files or a direct `SKILL.md`. Paths must remain inside the extension directory. |
150 | `enabled` | optional, defaults to `true`. set to `false` to disable without removing. |152 | `enabled` | optional, defaults to `true`. set to `false` to disable without removing. |
151 153
152 ## Lifecycle154 ## Lifecycle
153 155
154-1. **Discovery**: zot reads every `extension.json` in the search dirs.156+1. **Discovery**: zut reads every `extension.json` in the search dirs.
155 2. **Spawn**: enabled extensions are launched as subprocesses. stderr157 2. **Spawn**: enabled extensions are launched as subprocesses. stderr
156- redirects to `$ZOT_HOME/logs/ext-<name>.log` (one file per158+ redirects to `$ZUT_HOME/logs/ext-<name>.log` (one file per
157 extension, append-mode).159 extension, append-mode).
158 3. **Hello handshake**: the extension's first stdout frame must be160 3. **Hello handshake**: the extension's first stdout frame must be
159- `hello`; zot replies with `hello_ack` containing the protocol161+ `hello` with `protocol_version: 2`; zut rejects a mismatched major
160- version, the active provider/model/cwd, and the extension's own162+ and replies with `hello_ack` containing the same protocol version,
161- data directory so it can persist files beside its manifest.163+ the active provider/model/cwd, and the extension's own data directory
164+ so it can persist files beside its manifest.
162 4. **Registration**: after receiving `hello_ack`, the extension sends165 4. **Registration**: after receiving `hello_ack`, the extension sends
163 `register_command`, `register_tool`, and subscription frames, then166 `register_command`, `register_tool`, and subscription frames, then
164 sends `ready`. First-come-first-served: a name already taken by a167 sends `ready`. First-come-first-served: a name already taken by a
165 built-in or by a previously-loaded extension is silently shadowed168 built-in or by a previously-loaded extension is silently shadowed
166 (logged in the extension's own log file).169 (logged in the extension's own log file).
167-5. **Runtime**: after `ready`, zot dispatches `command_invoked` frames170+5. **Runtime**: after `ready`, zut dispatches `command_invoked` frames
168 when the user runs a registered command; the extension responds171 when the user runs a registered command; the extension responds
169 with `command_response`. Extensions can also push `notify`, persistent172 with `command_response`. Extensions can also push `notify`, persistent
170 status/widget, and structured `alert` frames during runtime. Extensions173 status/widget, and structured `alert` frames during runtime. Extensions
@@ -172,12 +175,12 @@ manifest tells zot how to launch it:
172 and their own restored opaque state. Panel-capable extensions may open an175 and their own restored opaque state. Panel-capable extensions may open an
173 interactive panel, receive key events, and push redraws while the panel is176 interactive panel, receive key events, and push redraws while the panel is
174 focused.177 focused.
175-6. **Shutdown**: when zot exits, it sends `shutdown` and waits up to178+6. **Shutdown**: when zut exits, it sends `shutdown` and waits up to
176 2s for the extension to send `shutdown_ack`. Holdouts are179 2s for the extension to send `shutdown_ack`. Holdouts are
177 SIGTERM'd, then SIGKILL'd.180 SIGTERM'd, then SIGKILL'd.
178 181
179-A crashing extension does not bring down zot. The slash command it182+A crashing extension does not bring down zut. The slash command it
180-owned simply stops working until the extension is fixed and zot is183+owned simply stops working until the extension is fixed and zut is
181 restarted.184 restarted.
182 185
183 ## Wire format186 ## Wire format
@@ -193,7 +196,7 @@ or any other stdout frame before `hello`.
193 #### `hello` (required, first frame)196 #### `hello` (required, first frame)
194 197
195 ```json198 ```json
196-{"type":"hello","name":"weather","version":"1.0.0",199+{"type":"hello","protocol_version":2,"name":"weather","version":"1.0.0",
197 "capabilities":["commands","tools","alerts","panels"]}200 "capabilities":["commands","tools","alerts","panels"]}
198 ```201 ```
199 202
@@ -204,7 +207,7 @@ or any other stdout frame before `hello`.
204 "description":"current weather for a city"}207 "description":"current weather for a city"}
205 ```208 ```
206 209
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.
208 211
209 #### `register_tool`212 #### `register_tool`
210 213
@@ -233,11 +236,11 @@ Set `"deferred": true` to register a tool without advertising its definition ini
233 "activate_tools":["weather"]}236 "activate_tools":["weather"]}
234 ```237 ```
235 238
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.
237 240
238 #### `ready`241 #### `ready`
239 242
240-Sentinel telling zot "all initial registrations are flushed". Send it243+Sentinel telling zut "all initial registrations are flushed". Send it
241 right after your last `register_*` frame so the host can build the244 right after your last `register_*` frame so the host can build the
242 agent's tool registry without racing the registration window.245 agent's tool registry without racing the registration window.
243 246
@@ -259,7 +262,7 @@ registered deferred tools that become available after this result.
259 "details":{"state":{"version":1}}}262 "details":{"state":{"version":1}}}
260 ```263 ```
261 264
262-`details` is opaque extension metadata. Zot excludes it from provider265+`details` is opaque extension metadata. Zut excludes it from provider
263 requests and persists it as the latest extension state for the active266 requests and persists it as the latest extension state for the active
264 session branch. Keep it valid JSON and reasonably small; the host caps267 session branch. Keep it valid JSON and reasonably small; the host caps
265 persisted snapshots at 256 KiB. The next `session_opened`,268 persisted snapshots at 256 KiB. The next `session_opened`,
@@ -284,9 +287,9 @@ Recognised event names include `session_start`, `session_opened`,
284 287
285 Session events contain `session` with the current branch ID, parent ID,288 Session events contain `session` with the current branch ID, parent ID,
286 path, cwd, and fork point. They also contain `state`, but only for the289 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.
288 291
289-`tool_confirmation_requested` fires only when zot is about to wait for292+`tool_confirmation_requested` fires only when zut is about to wait for
290 interactive approval. Calls running in yolo mode, calls covered by a293 interactive approval. Calls running in yolo mode, calls covered by a
291 remembered approval, and calls blocked before confirmation do not emit it.294 remembered approval, and calls blocked before confirmation do not emit it.
292 The event includes `tool_id`, `tool_name`, and the short `tool_preview`295 The event includes `tool_id`, `tool_name`, and the short `tool_preview`
@@ -353,9 +356,9 @@ subsequent interceptor sees the previous one's output.
353 - `"display"` — appends `display` to the chat as a one-shot styled356 - `"display"` — appends `display` to the chat as a one-shot styled
354 note. No model call, nothing written to the transcript.357 note. No model call, nothing written to the transcript.
355 - `"open_panel"` — opens an extension-owned interactive panel inside358 - `"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`.
357 - `"noop"` — the extension handled it itself (e.g. it pushed360 - `"noop"` — the extension handled it itself (e.g. it pushed
358- `notify` frames or kicked off background work). zot doesn't change361+ `notify` frames or kicked off background work). zut doesn't change
359 the UI in response.362 the UI in response.
360 363
361 Example:364 Example:
@@ -370,13 +373,13 @@ Example:
370 }}373 }}
371 ```374 ```
372 375
373-If `error` is non-empty, zot renders it as a red status line376+If `error` is non-empty, zut renders it as a red status line
374 regardless of `action`.377 regardless of `action`.
375 378
376 #### `submit` (one-way, any time)379 #### `submit` (one-way, any time)
377 380
378 Submits text as a user prompt in the interactive host. If the agent is381 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, zot382+idle, zut starts a turn immediately. If a turn is already running, zut
380 queues the prompt behind it using the same queue path as typed input.383 queues the prompt behind it using the same queue path as typed input.
381 Empty or whitespace-only text is ignored.384 Empty or whitespace-only text is ignored.
382 385
@@ -419,7 +422,7 @@ Sets or replaces one status item owned by the extension. Sending an empty
419 #### `widget` (one-way, persistent)422 #### `widget` (one-way, persistent)
420 423
421 Sets or replaces a compact widget. `position` is a host-defined placement424 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:
423 426
424 - `"above_input"` keeps the widget in the existing persistent chrome above427 - `"above_input"` keeps the widget in the existing persistent chrome above
425 the editor.428 the editor.
@@ -498,18 +501,19 @@ Sent in response to `shutdown`. Extension should exit promptly after.
498 #### `hello_ack`501 #### `hello_ack`
499 502
500 ```json503 ```json
501-{"type":"hello_ack","protocol_version":1,504+{"type":"hello_ack","protocol_version":2,
502- "zot_version":"0.0.7","provider":"anthropic",505+ "zut_version":"0.0.7","provider":"anthropic",
503- "model":"claude-opus-4-7","cwd":"/Users/pat/Developer/zot",506+ "model":"claude-opus-4-7","cwd":"/path/to/zut",
504- "extension_dir":"/Users/pat/Developer/zot/.zot/extensions/todos",507+ "extension_dir":"/path/to/zut/.zut/extensions/todos",
505- "data_dir":"/Users/pat/Developer/zot/.zot/extensions/todos",508+ "data_dir":"/path/to/zut/.zut/extensions/todos",
506 "session":{"id":"branch-1","parent_id":"root-1",509 "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",
508 "fork_point":4}}511 "fork_point":4}}
509 ```512 ```
510 513
511-Sent immediately after `hello`. Wait for this frame before sending514+Sent immediately after a matching `hello`. The protocol version is a
512-registrations if they depend on host metadata. The extension can use515+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
513 these fields to decide which commands to register (e.g. only register517 these fields to decide which commands to register (e.g. only register
514 a Python tool on macOS, only register a model-specific shortcut for518 a Python tool on macOS, only register a model-specific shortcut for
515 opus, etc.). `cwd` is the user's project directory; the extension519 opus, etc.). `cwd` is the user's project directory; the extension
@@ -562,7 +566,7 @@ Lifecycle notification for events the extension subscribed to via
562 566
563 #### `event_intercept`567 #### `event_intercept`
564 568
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,
566 or annotate a lifecycle event before it happens. Reply with570 or annotate a lifecycle event before it happens. Reply with
567 `event_intercept_response` within 5s; missing the deadline is571 `event_intercept_response` within 5s; missing the deadline is
568 treated as "allow".572 treated as "allow".
@@ -598,7 +602,7 @@ name (`up`, `down`, `left`, `right`, `enter`, `esc`, `tab`, `pageup`,
598 602
599 #### `panel_close`603 #### `panel_close`
600 604
601-Sent when the user closes the focused panel from zot (for example with605+Sent when the user closes the focused panel from zut (for example with
602 Esc or Ctrl+C). The extension should treat this as the panel lifetime606 Esc or Ctrl+C). The extension should treat this as the panel lifetime
603 ending and stop sending `panel_render` updates for that `panel_id`.607 ending and stop sending `panel_render` updates for that `panel_id`.
604 608
@@ -608,29 +612,29 @@ ending and stop sending `panel_render` updates for that `panel_id`.
608 612
609 #### `shutdown`613 #### `shutdown`
610 614
611-Sent during graceful zot exit (or `/reload-ext` once that lands).615+Sent during graceful zut exit (or `/reload-ext` once that lands).
612 Reply with `shutdown_ack` and then exit.616 Reply with `shutdown_ack` and then exit.
613 617
614 ## Managing extensions from the CLI618 ## Managing extensions from the CLI
615 619
616 ```620 ```
617-zot ext list list installed extensions and their state621+zut ext list list installed extensions and their state
618-zot ext doctor diagnose load, registration, and conflict issues622+zut ext doctor diagnose load, registration, and conflict issues
619-zot ext install <path|git-url> copy / clone into $ZOT_HOME/extensions/623+zut ext install <path|git-url> copy / clone into $ZUT_HOME/extensions/
620-zot ext install --build=go <local-path> build local Go source, then install624+zut ext install --build=go <local-path> build local Go source, then install
621-zot ext remove <name> delete an extension directory625+zut ext remove <name> delete an extension directory
622-zot ext enable <name> re-enable a disabled extension626+zut ext enable <name> re-enable a disabled extension
623-zot ext disable <name> disable without removing627+zut ext disable <name> disable without removing
624-zot ext logs <name> [-f] cat / tail the extension's stderr628+zut ext logs <name> [-f] cat / tail the extension's stderr
625 ```629 ```
626 630
627-`zot ext doctor` runs the same discovery path as zot startup, but reports631+`zut ext doctor` runs the same discovery path as zut startup, but reports
628 what happened instead of changing the fail-soft runtime behavior. It shows632 what happened instead of changing the fail-soft runtime behavior. It shows
629 manifest errors, disabled or shadowed extensions, subprocess load errors,633 manifest errors, disabled or shadowed extensions, subprocess load errors,
630 ready/auto-ready status, registered commands/tools, registration conflicts,634 ready/auto-ready status, registered commands/tools, registration conflicts,
631 warnings, and each extension's stderr log path.635 warnings, and each extension's stderr log path.
632 636
633-`zot ext install <path>` does a recursive copy; `<git-url>` does a637+`zut ext install <path>` does a recursive copy; `<git-url>` does a
634 shallow clone. Both validate that the destination contains an638 shallow clone. Both validate that the destination contains an
635 `extension.json` and roll back if not. No build runs unless the local install639 `extension.json` and roll back if not. No build runs unless the local install
636 is invoked with the explicit `--build=go` option. That option currently supports640 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.
639 ## Loading an extension for one run643 ## Loading an extension for one run
640 644
641 For iteration on a working copy, skip the install + reload cycle645 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:
643 647
644 ```648 ```
645-zot --ext ./my-extension # short form: -e ./my-extension649+zut --ext ./my-extension # short form: -e ./my-extension
646-zot --ext ./a -e ./b # repeatable650+zut --ext ./a -e ./b # repeatable
647 ```651 ```
648 652
649 `--ext` paths take precedence over installed extensions of the same653 `--ext` paths take precedence over installed extensions of the same
650 name, so you can shadow an installed copy with a work-in-progress654 name, so you can shadow an installed copy with a work-in-progress
651 version without uninstalling first. Nothing is copied or persisted;655 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.
653 657
654 ## SDKs658 ## SDKs
655 659
@@ -663,7 +667,7 @@ package main
663 667
664 import (668 import (
665 "encoding/json"669 "encoding/json"
666- "github.com/patriceckhart/zot/packages/agent/ext"670+ "github.com/bnema/zut/packages/agent/ext"
667 )671 )
668 672
669 func main() {673 func main() {
@@ -697,10 +701,10 @@ func main() {
697 ```701 ```
698 702
699 Build with `go build -o hello .`, drop the binary + an `extension.json`703 Build with `go build -o hello .`, drop the binary + an `extension.json`
700-into `$ZOT_HOME/extensions/hello/`.704+into `$ZUT_HOME/extensions/hello/`.
701 705
702 `OnHello` is optional. Use it when configuration or registrations need706 `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`,
704 `ExtensionDir`, or `DataDir`. The SDK sends `hello`, waits for708 `ExtensionDir`, or `DataDir`. The SDK sends `hello`, waits for
705 `hello_ack`, runs `OnHello`, announces registrations, then sends `ready`.709 `hello_ack`, runs `OnHello`, announces registrations, then sends `ready`.
706 710
@@ -770,7 +774,7 @@ The `tasked-phases` example bundles its companion skill under
770 `extension.json`. Installing the extension therefore installs the workflow774 `extension.json`. Installing the extension therefore installs the workflow
771 skill automatically. The standalone copy at `examples/skills/tasked-phases/`775 skill automatically. The standalone copy at `examples/skills/tasked-phases/`
772 remains useful for project-local skill installation. Its state is restored per776 remains useful for project-local skill installation. Its state is restored per
773-session branch when zot session persistence is enabled; the extension keeps a777+session branch when zut session persistence is enabled; the extension keeps a
774 project-file fallback for hosts that do not persist sessions.778 project-file fallback for hosts that do not persist sessions.
775 779
776 ### Hot reload780 ### Hot reload
@@ -778,7 +782,7 @@ project-file fallback for hosts that do not persist sessions.
778 Type `/reload-ext` in the TUI to tear down every running extension782 Type `/reload-ext` in the TUI to tear down every running extension
779 subprocess, re-read the manifests from disk, and respawn the set.783 subprocess, re-read the manifests from disk, and respawn the set.
780 The agent's tool registry is rebuilt automatically, so freshly-784 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.
782 Useful while developing an extension: edit, save, `/reload-ext`,786 Useful while developing an extension: edit, save, `/reload-ext`,
783 done. Explicit `--ext` paths are remembered and reloaded alongside787 done. Explicit `--ext` paths are remembered and reloaded alongside
784 discovered extensions. The temporary reload status reports each load788 discovered extensions. The temporary reload status reports each load
@@ -803,9 +807,9 @@ Extensions run with **the user's full filesystem and network
803 permissions**. Treat installing an extension the same as installing807 permissions**. Treat installing an extension the same as installing
804 any other binary on your machine.808 any other binary on your machine.
805 809
806-`zot ext install <git-url>` clones from any URL you give it. There's810+`zut ext install <git-url>` clones from any URL you give it. There's
807 no sandbox in v1; if you need isolation, install only extensions you811 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` /
809 `sandbox-exec` / AppContainer).813 `sandbox-exec` / AppContainer).
810 814
811 ## Roadmap815 ## Roadmap
@@ -814,7 +818,7 @@ Phase 1 (shipped):
814 - [x] subprocess lifecycle + hello handshake818 - [x] subprocess lifecycle + hello handshake
815 - [x] `register_command` + `command_invoked`819 - [x] `register_command` + `command_invoked`
816 - [x] `notify` + `clear_notes` + structured terminal alerts820 - [x] `notify` + `clear_notes` + structured terminal alerts
817-- [x] `zot ext` CLI821+- [x] `zut ext` CLI
818 822
819 Phase 2 (shipped):823 Phase 2 (shipped):
820 - [x] `register_tool` + `tool_call` + `tool_result`824 - [x] `register_tool` + `tool_call` + `tool_result`
@@ -832,7 +836,7 @@ Phase 4 (shipped):
832 addition to `tool_call`)836 addition to `tool_call`)
833 - [x] modify tool args mid-flight via `modified_args`837 - [x] modify tool args mid-flight via `modified_args`
834 - [x] rewrite user-visible assistant text via `replace_text`838 - [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)
836 840
837 Phase 5 (shipped):841 Phase 5 (shipped):
838 - [x] session/branch identity in `hello_ack` and lifecycle events842 - [x] session/branch identity in `hello_ack` and lifecycle events
M docs/lsp.md +8 -8
@@ -1,18 +1,18 @@
1 # LSP and linter support1 # LSP and linter support
2 2
3-zot exposes a built-in `lsp` tool for code intelligence and diagnostics. It3+zut exposes a built-in `lsp` tool for code intelligence and diagnostics. It
4 communicates with stdio language servers through JSON-RPC and can also run4 communicates with stdio language servers through JSON-RPC and can also run
5 configured command-line linters without a shell.5 configured command-line linters without a shell.
6 6
7 ## Configuration7 ## Configuration
8 8
9-zot loads JSON configuration from the following layers, in increasing9+zut loads JSON configuration from the following layers, in increasing
10 precedence:10 precedence:
11 11
12-- `$ZOT_HOME/lsp.json`12+- `$ZUT_HOME/lsp.json`
13 - `<cwd>/.pi/lsp.json`13 - `<cwd>/.pi/lsp.json`
14 - `<cwd>/.omp/lsp.json`14 - `<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`
16 16
17 A file may put providers below `servers`, `lspServers`, or `linters`, use a17 A file may put providers below `servers`, `lspServers`, or `linters`, use a
18 flat provider map, or use the `providers` array:18 flat provider map, or use the `providers` array:
@@ -43,7 +43,7 @@ The `providers` array accepts built-in names and objects with `id`, `kind`,
43 Unknown provider metadata is ignored. Set `autoDetect` to `false` to43 Unknown provider metadata is ignored. Set `autoDetect` to `false` to
44 keep only providers named or defined by the configuration file.44 keep only providers named or defined by the configuration file.
45 45
46-Server overrides merge onto zot's built-in definitions. Built-ins cover common46+Server overrides merge onto zut's built-in definitions. Built-ins cover common
47 Go, Rust, TypeScript/JavaScript, Python, C/C++, YAML, JSON, and shell servers,47 Go, Rust, TypeScript/JavaScript, Python, C/C++, YAML, JSON, and shell servers,
48 plus ESLint, golangci-lint, and Ruff CLI providers. A provider is selected48 plus ESLint, golangci-lint, and Ruff CLI providers. A provider is selected
49 when it is enabled and its file type/root markers match the workspace. Command49 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.
125 125
126 The sub-agent setting does not grant access to a child whose profile or126 The sub-agent setting does not grant access to a child whose profile or
127 explicit tool list omits `lsp`. Both settings can also be controlled through127 explicit tool list omits `lsp`. Both settings can also be controlled through
128-`$ZOT_HOME/config.json`.128+`$ZUT_HOME/config.json`.
129 129
130 ## Safety and lifecycle130 ## Safety and lifecycle
131 131
132 Language servers and linters run as local child processes. Project and132 Language servers and linters run as local child processes. Project and
133 global LSP configuration is executable local configuration: review configured133 global LSP configuration is executable local configuration: review configured
134 commands, arguments, environment variables, and workspace-local binaries before134 commands, arguments, environment variables, and workspace-local binaries before
135-using it. zot does not add a separate trust prompt or sandbox around those child135+using it. zut does not add a separate trust prompt or sandbox around those child
136 processes; it does avoid shell expansion and keeps returned workspace edits136 processes; it does avoid shell expansion and keeps returned workspace edits
137 inside the session workspace. The `lsp` tool never executes configured commands137 inside the session workspace. The `lsp` tool never executes configured commands
138 through a shell. Workspace edits returned by138 through a shell. Workspace edits returned by
@@ -141,7 +141,7 @@ resource operations, validate UTF-16 LSP positions, and write through a
141 temporary file before replacement. Server-initiated `workspace/applyEdit`141 temporary file before replacement. Server-initiated `workspace/applyEdit`
142 requests use the same checks.142 requests use the same checks.
143 143
144-LSP clients are cached per workspace and provider for the lifetime of the zot144+LSP clients are cached per workspace and provider for the lifetime of the zut
145 process. `reload` and process shutdown terminate cached children. Server145 process. `reload` and process shutdown terminate cached children. Server
146 stderr is kept out of model-facing tool results; diagnostics and protocol146 stderr is kept out of model-facing tool results; diagnostics and protocol
147 errors are returned through the established tool result channel.147 errors are returned through the established tool result channel.
M docs/providers.md +31 -31
@@ -1,14 +1,14 @@
1-# zot providers1+# zut providers
2 2
3-zot ships with built-in providers and a model catalog. You can select models3+zut 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 in4+with `/model`, list them with `zut --list-models`, and add private models in
5-`$ZOT_HOME/models.json`.5+`$ZUT_HOME/models.json`.
6 6
7 ## Login methods7 ## Login methods
8 8
9 Use `/login` in interactive mode. Type in either provider picker to filter the list by provider ID or display name.9 Use `/login` in interactive mode. Type in either provider picker to filter the list by provider ID or display name.
10 10
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.
12 - `subscription`: stores OAuth credentials for subscription-backed providers.12 - `subscription`: stores OAuth credentials for subscription-backed providers.
13 13
14 Use `/logout` to remove stored credentials.14 Use `/logout` to remove stored credentials.
@@ -20,7 +20,7 @@ sessions.
20 20
21 ### Command-backed API keys21 ### Command-backed API keys
22 22
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`:
24 24
25 ```json25 ```json
26 {26 {
@@ -49,9 +49,9 @@ Custom provider credentials use the same object under `additional_api_key_creds`
49 }49 }
50 ```50 ```
51 51
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.
53 53
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.
55 55
56 Setup-instruction providers:56 Setup-instruction providers:
57 57
@@ -73,7 +73,7 @@ These providers support subscription login:
73 | xAI | SuperGrok or X Premium device-code login. The browser URL is prefilled with the device code. |73 | xAI | SuperGrok or X Premium device-code login. The browser URL is prefilled with the device code. |
74 | GitHub Copilot | GitHub Copilot token flow. |74 | GitHub Copilot | GitHub Copilot token flow. |
75 75
76-OAuth tokens are stored in `$ZOT_HOME/auth.json` and refreshed when refresh is76+OAuth tokens are stored in `$ZUT_HOME/auth.json` and refreshed when refresh is
77 available.77 available.
78 78
79 ## API-key providers79 ## API-key providers
@@ -119,7 +119,7 @@ show instructions and should be configured with environment variables.
119 119
120 OpenAI, OpenAI Responses, and OpenAI Codex requests can opt into OpenAI's120 OpenAI, OpenAI Responses, and OpenAI Codex requests can opt into OpenAI's
121 Fast service tier from `/settings` by enabling **fast mode**. The setting is121 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`.
123 123
124 Fast mode is currently limited to those OpenAI provider IDs. Enabling it while124 Fast mode is currently limited to those OpenAI provider IDs. Enabling it while
125 using another provider returns an error instead of silently changing that125 using another provider returns an error instead of silently changing that
@@ -129,7 +129,7 @@ Example:
129 129
130 ```bash130 ```bash
131 export OPENROUTER_API_KEY=...131 export OPENROUTER_API_KEY=...
132-zot --provider openrouter132+zut --provider openrouter
133 ```133 ```
134 134
135 ## Local llama.cpp router135 ## Local llama.cpp router
@@ -143,7 +143,7 @@ brew install llama.cpp
143 # use `brew upgrade llama.cpp` for an existing installation143 # use `brew upgrade llama.cpp` for an existing installation
144 ```144 ```
145 145
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.
147 147
148 ```bash148 ```bash
149 mkdir -p ~/llama-models149 mkdir -p ~/llama-models
@@ -160,7 +160,7 @@ llama-server \
160 160
161 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.161 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.
162 162
163-Check the management API before opening zot:163+Check the management API before opening zut:
164 164
165 ```bash165 ```bash
166 curl http://127.0.0.1:8080/health166 curl http://127.0.0.1:8080/health
@@ -169,7 +169,7 @@ curl http://127.0.0.1:8080/models
169 169
170 `/models` must return JSON with a `data` array. A 404 indicates the wrong URL, an older server build, or single-model mode.170 `/models` must return JSON with a `data` array. A 404 indicates the wrong URL, an older server build, or single-model mode.
171 171
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:
173 173
174 ```bash174 ```bash
175 export LLAMA_BASE_URL=http://127.0.0.1:8080175 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
188 188
189 ### Amazon Bedrock189 ### Amazon Bedrock
190 190
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.
192 Use one of these credential sources:192 Use one of these credential sources:
193 193
194 ```bash194 ```bash
@@ -214,13 +214,13 @@ Example:
214 214
215 ```bash215 ```bash
216 AWS_BEARER_TOKEN_BEDROCK=bedrock-api-key-... AWS_REGION=us-east-1 \216 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:0217+ zut --provider amazon-bedrock --model anthropic.claude-sonnet-4-5-20250929-v1:0
218 ```218 ```
219 219
220 Some Bedrock models require regional inference-profile IDs for on-demand220 Some Bedrock models require regional inference-profile IDs for on-demand
221-throughput, such as `us.` or `eu.` prefixed model IDs. zot rewrites known221+throughput, such as `us.` or `eu.` prefixed model IDs. zut rewrites known
222 families automatically where possible. Claude Opus 5 uses222 families automatically where possible. Claude Opus 5 uses
223-`global.anthropic.claude-opus-5`; zot maps the bare foundation-model ID to that223+`global.anthropic.claude-opus-5`; zut maps the bare foundation-model ID to that
224 global profile and supports its adaptive reasoning levels. Explicit profile IDs224 global profile and supports its adaptive reasoning levels. Explicit profile IDs
225 and ARNs are left unchanged.225 and ARNs are left unchanged.
226 226
@@ -230,7 +230,7 @@ Vertex can use a Google API key when available:
230 230
231 ```bash231 ```bash
232 export GOOGLE_CLOUD_API_KEY=...232 export GOOGLE_CLOUD_API_KEY=...
233-zot --provider google-vertex233+zut --provider google-vertex
234 ```234 ```
235 235
236 For service-account or application-default credentials, set the standard236 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:
244 export CLOUDFLARE_API_KEY=...244 export CLOUDFLARE_API_KEY=...
245 export CLOUDFLARE_ACCOUNT_ID=...245 export CLOUDFLARE_ACCOUNT_ID=...
246 export CLOUDFLARE_GATEWAY_ID=...246 export CLOUDFLARE_GATEWAY_ID=...
247-zot --provider cloudflare-ai-gateway247+zut --provider cloudflare-ai-gateway
248 ```248 ```
249 249
250 ### Cloudflare Workers AI250 ### Cloudflare Workers AI
@@ -254,7 +254,7 @@ Workers AI needs a Cloudflare token and account ID:
254 ```bash254 ```bash
255 export CLOUDFLARE_API_KEY=...255 export CLOUDFLARE_API_KEY=...
256 export CLOUDFLARE_ACCOUNT_ID=...256 export CLOUDFLARE_ACCOUNT_ID=...
257-zot --provider cloudflare-workers-ai257+zut --provider cloudflare-workers-ai
258 ```258 ```
259 259
260 ### Azure OpenAI Responses260 ### Azure OpenAI Responses
@@ -263,10 +263,10 @@ zot --provider cloudflare-workers-ai
263 export AZURE_OPENAI_API_KEY=...263 export AZURE_OPENAI_API_KEY=...
264 export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com264 export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com
265 export AZURE_OPENAI_API_VERSION=v1 # optional, v1 is the default265 export AZURE_OPENAI_API_VERSION=v1 # optional, v1 is the default
266-zot --provider azure-openai-responses266+zut --provider azure-openai-responses
267 ```267 ```
268 268
269-The provider uses Azure's Responses API. If deployment names differ from zot269+The provider uses Azure's Responses API. If deployment names differ from zut
270 model IDs, map them without changing the catalog:270 model IDs, map them without changing the catalog:
271 271
272 ```bash272 ```bash
@@ -275,8 +275,8 @@ export AZURE_OPENAI_DEPLOYMENT_NAME_MAP='gpt-5.6-luna=luna-preview,gpt-5.6-sol=s
275 275
276 ## Auth file276 ## Auth file
277 277
278-Credentials are stored in `$ZOT_HOME/auth.json` with user-only permissions278+Credentials are stored in `$ZUT_HOME/auth.json` with user-only permissions
279-when zot creates the file.279+when zut creates the file.
280 280
281 Example:281 Example:
282 282
@@ -294,11 +294,11 @@ Example:
294 294
295 The top-level keys are used for providers with dedicated credential fields.295 The top-level keys are used for providers with dedicated credential fields.
296 Other API-key providers are stored under `additional_api_key_creds`. Prefer296 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.
298 298
299 ## Custom providers and models299 ## Custom providers and models
300 300
301-Use `$ZOT_HOME/models.json` for private models, deployment aliases, local301+Use `$ZUT_HOME/models.json` for private models, deployment aliases, local
302 servers, or OpenAI-compatible gateways that are not in the built-in catalog.302 servers, or OpenAI-compatible gateways that are not in the built-in catalog.
303 User entries override built-in entries with the same provider and model ID, and303 User entries override built-in entries with the same provider and model ID, and
304 adding a `models.json` no longer hides the built-in catalog: your entries are304 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
333 without a verification probe; an invalid key surfaces on the first model call.333 without a verification probe; an invalid key surfaces on the first model call.
334 334
335 To retrieve this custom provider's key from a password manager, add a matching335 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`:
337 337
338 ```json338 ```json
339 {339 {
@@ -353,17 +353,17 @@ The provider IDs in `models.json` and `auth.json` must match. Then select the
353 custom model directly:353 custom model directly:
354 354
355 ```sh355 ```sh
356-zot --provider my-company --model company-llm-v2356+zut --provider my-company --model company-llm-v2
357 ```357 ```
358 358
359 ## Credential resolution359 ## Credential resolution
360 360
361-For each request, zot checks credentials in this order:361+For each request, zut checks credentials in this order:
362 362
363 1. Explicit CLI key, such as `--api-key`.363 1. Explicit CLI key, such as `--api-key`.
364 2. Provider-specific environment variables (including derived custom-provider364 2. Provider-specific environment variables (including derived custom-provider
365 variables such as `MY_COMPANY_API_KEY`).365 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`.
367 367
368 `models.json` itself never stores credentials; it only describes models and368 `models.json` itself never stores credentials; it only describes models and
369 their endpoints.369 their endpoints.
M docs/rpc.md +12 -12
@@ -1,41 +1,41 @@
1-# zot RPC1+# zut RPC
2 2
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.
4 4
5 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.5 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.
6 6
7 ## Quick start7 ## Quick start
8 8
9 ```bash9 ```bash
10-# spawn zot rpc; talk to it from a shell10+# spawn zut rpc; talk to it from a shell
11 ( echo '{"id":"1","type":"prompt","message":"hello"}'; sleep 5 ) \11 ( echo '{"id":"1","type":"prompt","message":"hello"}'; sleep 5 ) \
12- | zot rpc --provider anthropic12+ | zut rpc --provider anthropic
13 ```13 ```
14 14
15 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`.15 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`.
16 16
17 ## Process model17 ## Process model
18 18
19-- One `zot rpc` process serves **one cwd, one model, one session**.19+- One `zut rpc` process serves **one cwd, one model, one session**.
20 - For multiple projects, spawn multiple processes.20 - For multiple projects, spawn multiple processes.
21 - Concurrency: at most one prompt or compact in flight at a time. A second one queues until the first finishes; aborting fires immediately.21 - Concurrency: at most one prompt or compact in flight at a time. A second one queues until the first finishes; aborting fires immediately.
22 - The process exits when stdin closes.22 - The process exits when stdin closes.
23 23
24 ## Flags24 ## Flags
25 25
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.
27 27
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.
29 29
30 ## Auth30 ## Auth
31 31
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:
33 33
34 ```json34 ```json
35 {"id":"0","type":"hello","token":"shared-secret"}35 {"id":"0","type":"hello","token":"shared-secret"}
36 ```36 ```
37 37
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).
39 39
40 ## Wire format40 ## Wire format
41 41
@@ -66,7 +66,7 @@ Response:
66 "data":{"protocol_version":1,"version":"0.0.4","provider":"anthropic","model":"claude-opus-4-5"}}66 "data":{"protocol_version":1,"version":"0.0.4","provider":"anthropic","model":"claude-opus-4-5"}}
67 ```67 ```
68 68
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.
70 70
71 ### `prompt`71 ### `prompt`
72 72
@@ -120,7 +120,7 @@ Response data:
120 {120 {
121 "provider": "anthropic",121 "provider": "anthropic",
122 "model": "claude-opus-4-5",122 "model": "claude-opus-4-5",
123- "cwd": "/Users/pat/Developer/zot",123+ "cwd": "/path/to/zut",
124 "message_count": 12,124 "message_count": 12,
125 "busy": false,125 "busy": false,
126 "usage": {"input": 1234, "output": 567, "reasoning": 123, "cache_read": 890, "cache_write": 0, "cost_usd": 0.0123}126 "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.
153 {"id":"7","type":"set_model","model":"claude-sonnet-4-5"}153 {"id":"7","type":"set_model","model":"claude-sonnet-4-5"}
154 ```154 ```
155 155
156-Cross-provider swaps require relaunching `zot rpc` with the new `--provider`.156+Cross-provider swaps require relaunching `zut rpc` with the new `--provider`.
157 157
158 ### `set_reasoning`158 ### `set_reasoning`
159 159
M docs/skills.md +16 -16
@@ -1,8 +1,8 @@
1-# zot skills1+# zut skills
2 2
3 A skill is a reusable instruction set written as a single3 A skill is a reusable instruction set written as a single
4 `SKILL.md` file with a YAML frontmatter header. Unless a skill disables4 `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 model5+model invocation, zut discovers it at startup and surfaces it to the model
6 in two ways:6 in two ways:
7 7
8 1. The system prompt gains a short manifest:8 1. The system prompt gains a short manifest:
@@ -49,22 +49,22 @@ There's no template engine; the model sees what you write.
49 49
50 ## Discovery50 ## Discovery
51 51
52-zot looks in these directories, in priority order, and registers the52+zut looks in these directories, in priority order, and registers the
53 first `SKILL.md` it finds for each unique name:53 first `SKILL.md` it finds for each unique name:
54 54
55 | location | scope |55 | location | scope |
56 |---|---|56 |---|---|
57-| `./.zot/skills/<name>/SKILL.md` | project (native) |57+| `./.zut/skills/<name>/SKILL.md` | project (native) |
58-| `$ZOT_HOME/skills/<name>/SKILL.md` | global (native) |58+| `$ZUT_HOME/skills/<name>/SKILL.md` | global (native) |
59 | `./.claude/skills/<name>/SKILL.md` | project (claude-compat) |59 | `./.claude/skills/<name>/SKILL.md` | project (claude-compat) |
60 | `~/.claude/skills/<name>/SKILL.md` | global (claude-compat) |60 | `~/.claude/skills/<name>/SKILL.md` | global (claude-compat) |
61 | `./.agents/skills/<name>/SKILL.md` | project (agent-compat) |61 | `./.agents/skills/<name>/SKILL.md` | project (agent-compat) |
62 | `~/.agents/skills/<name>/SKILL.md` | global (agent-compat) |62 | `~/.agents/skills/<name>/SKILL.md` | global (agent-compat) |
63 63
64 The compat paths are deliberate: a `SKILL.md` written for an existing64 The compat paths are deliberate: a `SKILL.md` written for an existing
65-skill ecosystem works in zot unchanged. Drop your existing65+skill ecosystem works in zut unchanged. Drop your existing
66 `.claude/skills/` or `.agents/skills/` directories into a project and66 `.claude/skills/` or `.agents/skills/` directories into a project and
67-zot will pick them up. User skill roots are scanned recursively, so67+zut will pick them up. User skill roots are scanned recursively, so
68 nested files such as `~/.agents/skills/systems-backend/subskills/golang-patterns/SKILL.md`68 nested files such as `~/.agents/skills/systems-backend/subskills/golang-patterns/SKILL.md`
69 are discovered too. The frontmatter `name` is the canonical identifier;69 are discovered too. The frontmatter `name` is the canonical identifier;
70 for nested user skills, the slash-separated directory path relative to the70 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
74 `extension.json`, for example `"skills": ["skills"]`, with files at74 `extension.json`, for example `"skills": ["skills"]`, with files at
75 `skills/<name>/SKILL.md`. A declared directory may also contain a single75 `skills/<name>/SKILL.md`. A declared directory may also contain a single
76 `SKILL.md` directly; its directory name supplies the fallback skill name and76 `SKILL.md` directly; its directory name supplies the fallback skill name and
77-child directories are not scanned. Bundled skills are copied by `zot ext77+child directories are not scanned. Bundled skills are copied by `zut ext
78 install` and loaded automatically. User/project skills take precedence over78 install` and loaded automatically. User/project skills take precedence over
79 bundled skills; bundled skills take precedence over embedded built-ins. When79 bundled skills; bundled skills take precedence over embedded built-ins. When
80 two extensions provide the same skill name, the first declaration wins. The80 two extensions provide the same skill name, the first declaration wins. The
81 host rejects manifest paths that escape the extension directory, including81 host rejects manifest paths that escape the extension directory, including
82 symlinked skill files that resolve outside it.82 symlinked skill files that resolve outside it.
83 83
84-When `XDG_STATE_HOME` is set on any platform, `$ZOT_HOME` defaults to84+When `XDG_STATE_HOME` is set on any platform, `$ZUT_HOME` defaults to
85-`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`85+`$XDG_STATE_HOME/zut`. Otherwise it defaults to `~/Library/Application Support/zut/`
86-on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.86+on macOS, `~/.local/state/zut` on Linux, or `%LOCALAPPDATA%\zut` on Windows.
87 87
88 ## Inspecting installed skills88 ## Inspecting installed skills
89 89
90-In zot, run `/skills`. A picker lists every discovered skill with its90+In zut, run `/skills`. A picker lists every discovered skill with its
91 description and source path. Press enter on a row to view the full91 description and source path. Press enter on a row to view the full
92 body inline. Press esc to go back.92 body inline. Press esc to go back.
93 93
@@ -104,7 +104,7 @@ To force a specific skill, invoke it as a slash command. Typing `/skill:` opens
104 /skill:code-review focus on security issues104 /skill:code-review focus on security issues
105 ```105 ```
106 106
107-zot expands the command into a user message containing the complete skill107+zut expands the command into a user message containing the complete skill
108 body, its directory for resolving relative references, and any text following108 body, its directory for resolving relative references, and any text following
109 the command as the request. This bypasses model-side skill selection.109 the command as the request. This bypasses model-side skill selection.
110 110
@@ -136,11 +136,11 @@ See `examples/skills/` for starter skills:
136 136
137 ## Comparison to other discovery layouts137 ## Comparison to other discovery layouts
138 138
139-| ecosystem | path | zot reads it? |139+| ecosystem | path | zut reads it? |
140 |---|---|---|140 |---|---|---|
141-| (native) | `.zot/skills/<name>/SKILL.md` | yes |141+| (native) | `.zut/skills/<name>/SKILL.md` | yes |
142 | (claude-style) | `.claude/skills/<name>/SKILL.md` | yes |142 | (claude-style) | `.claude/skills/<name>/SKILL.md` | yes |
143 | (agent-style) | `.agents/skills/<name>/SKILL.md` | yes |143 | (agent-style) | `.agents/skills/<name>/SKILL.md` | yes |
144 144
145 Cross-pollination is intentional: pick whichever convention you're145 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 @@
1 # Named subagent profiles1 # Named subagent profiles
2 2
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.
4 4
5 ## Discovery5 ## Discovery
6 6
@@ -10,12 +10,12 @@ The default directory is:
10 ~/.agents/agents/*.md10 ~/.agents/agents/*.md
11 ```11 ```
12 12
13-zot also reads these optional locations:13+zut also reads these optional locations:
14 14
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)
16 - `~/.pi/agent/agents/*.md` as a compatibility fallback16 - `~/.pi/agent/agents/*.md` as a compatibility fallback
17 17
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.
19 19
20 ## File format20 ## File format
21 21
@@ -41,7 +41,7 @@ Supported metadata:
41 |---|---|41 |---|---|
42 | `name` | Name passed to `subagent_spawn`'s `agent` field. Falls back to the filename. |42 | `name` | Name passed to `subagent_spawn`'s `agent` field. Falls back to the filename. |
43 | `description` | Short description shown to the main agent. |43 | `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. |
45 | `model` | Optional model ID. A qualified value such as `openai-codex/gpt-5.6-luna` selects both provider and model. |45 | `model` | Optional model ID. A qualified value such as `openai-codex/gpt-5.6-luna` selects both provider and model. |
46 | `provider` | Optional separate provider ID for a model without a provider prefix. |46 | `provider` | Optional separate provider ID for a model without a provider prefix. |
47 | `thinking` / `reasoning` | Optional reasoning level: `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, or `max`. |47 | `thinking` / `reasoning` | Optional reasoning level: `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, or `max`. |
@@ -50,7 +50,7 @@ Supported metadata:
50 | `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. |50 | `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. |
51 | `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. |51 | `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. |
52 52
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.
54 54
55 ## Selecting a profile55 ## Selecting a profile
56 56
@@ -84,7 +84,7 @@ The interactive command also supports the same selection explicitly:
84 /subagents new --agent implementer --reasoning high Implement the parser change84 /subagents new --agent implementer --reasoning high Implement the parser change
85 ```85 ```
86 86
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.
88 88
89 ## Lifecycle, results, and recovery89 ## Lifecycle, results, and recovery
90 90
@@ -107,4 +107,4 @@ When auto-subagents is enabled, the model also receives the read-only `subagent_
107 107
108 ## Resource policy108 ## Resource policy
109 109
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 themes1+# zut themes
2 2
3-zot themes are JSON files that override any subset of the built-in3+zut themes are JSON files that override any subset of the built-in
4 light/dark TUI theme. Nothing is required: a theme can change one4 light/dark TUI theme. Nothing is required: a theme can change one
5 color, only the spinner, only syntax highlighting, or all of them.5 color, only the spinner, only syntax highlighting, or all of them.
6-Missing values inherit from zot's built-in default for the detected6+Missing values inherit from zut's built-in default for the detected
7 terminal background.7 terminal background.
8 8
9 ## Where themes live9 ## Where themes live
@@ -11,32 +11,32 @@ terminal background.
11 User themes are discovered from:11 User themes are discovered from:
12 12
13 ```text13 ```text
14-$ZOT_HOME/themes/*.json14+$ZUT_HOME/themes/*.json
15 ```15 ```
16 16
17 Open `/settings` and choose **color theme** to switch. Changes are17 Open `/settings` and choose **color theme** to switch. Changes are
18-saved in `$ZOT_HOME/config.json` and apply immediately. If the selected18+saved in `$ZUT_HOME/config.json` and apply immediately. If the selected
19-file is deleted later, zot resets the setting to the built-in auto19+file is deleted later, zut resets the setting to the built-in auto
20 (default) theme.20 (default) theme.
21 21
22 The built-in choices are:22 The built-in choices are:
23 23
24-- **auto** — detect the terminal background and use zot's built-in dark or24+- **auto** — detect the terminal background and use zut's built-in dark or
25 light palette. This remains the default.25 light palette. This remains the default.
26 - **inherited (from terminal)** — use the terminal's reported foreground,26 - **inherited (from terminal)** — use the terminal's reported foreground,
27 background, and ANSI palette. When `COLORTERM=truecolor`/`24bit` or27 background, and ANSI palette. When `COLORTERM=truecolor`/`24bit` or
28- `TERM` ends in `-direct`, zot emits truecolor; otherwise it quantizes28+ `TERM` ends in `-direct`, zut emits truecolor; otherwise it quantizes
29 derived colors to the xterm-256 palette as a best effort. Terminals that29 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 while30+ do not answer OSC color queries fall back to zut's built-in palette while
31 still using the advertised color depth.31 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.
33 33
34 The inherited terminal snapshot is captured at startup and reused when the34 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.
36 36
37 All theme color roles use the same terminal-color model. JSON themes may use37 All theme color roles use the same terminal-color model. JSON themes may use
38 xterm-256 indexes, ANSI SGR colors, or RGB values for any role. On terminals38 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 indexed39+that advertise truecolor, zut emits RGB values directly and expands indexed
40 values through the xterm palette; otherwise RGB values fall back to the40 values through the xterm palette; otherwise RGB values fall back to the
41 nearest xterm-256 color while indexed values remain indexed. ANSI values keep41 nearest xterm-256 color while indexed values remain indexed. ANSI values keep
42 their SGR semantics, with standard foreground/background ranges normalized to42 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
52 extension directories:52 extension directories:
53 53
54 ```text54 ```text
55-$ZOT_HOME/extensions/<extension>/theme.json55+$ZUT_HOME/extensions/<extension>/theme.json
56-$ZOT_HOME/extensions/<extension>/themes/theme.json56+$ZUT_HOME/extensions/<extension>/themes/theme.json
57-<project>/.zot/extensions/<extension>/theme.json57+<project>/.zut/extensions/<extension>/theme.json
58-<project>/.zot/extensions/<extension>/themes/theme.json58+<project>/.zut/extensions/<extension>/themes/theme.json
59 ```59 ```
60 60
61-zot does **not** copy extension themes into `$ZOT_HOME/themes`; extension61+zut does **not** copy extension themes into `$ZUT_HOME/themes`; extension
62 owned themes stay in the extension directory. The settings picker shows62 owned themes stay in the extension directory. The settings picker shows
63 source info such as `from extension my-theme-extension`.63 source info such as `from extension my-theme-extension`.
64 64
@@ -71,7 +71,7 @@ Metadata only:
71 ```json71 ```json
72 {72 {
73 "name": "my-theme",73 "name": "my-theme",
74- "description": "Metadata only; all visuals inherit zot defaults."74+ "description": "Metadata only; all visuals inherit zut defaults."
75 }75 }
76 ```76 ```
77 77
@@ -111,13 +111,13 @@ Spinner-only:
111 ```111 ```
112 112
113 Dark-only themes still work on light terminals. If `colors.light` is113 Dark-only themes still work on light terminals. If `colors.light` is
114-missing, zot applies `colors.dark` overrides on top of the built-in114+missing, zut applies `colors.dark` overrides on top of the built-in
115 light default. The inverse also works.115 light default. The inverse also works.
116 116
117 ```json117 ```json
118 {118 {
119 "name": "custom-spinner",119 "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.",
121 "colors": {121 "colors": {
122 "dark": {122 "dark": {
123 "spinner_frames": ["◢", "◣", "◤", "◥"],123 "spinner_frames": ["◢", "◣", "◤", "◥"],
@@ -137,7 +137,7 @@ All fields are optional.
137 "name": "my-theme",137 "name": "my-theme",
138 "description": "Shown in /settings → color theme.",138 "description": "Shown in /settings → color theme.",
139 "color_descriptions": {139 "color_descriptions": {
140- "accent": "Optional documentation for humans. zot ignores this object."140+ "accent": "Optional documentation for humans. zut ignores this object."
141 },141 },
142 "colors": {142 "colors": {
143 "dark": {143 "dark": {
@@ -225,11 +225,11 @@ selected surfaces from the terminal foreground/background.
225 - `fg` — default foreground text.225 - `fg` — default foreground text.
226 - `muted` — secondary text, dividers, gutters, inactive hints.226 - `muted` — secondary text, dividers, gutters, inactive hints.
227 - `accent` — prompt bar, bullets, links, headings, active markers.227 - `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.
229 - `user` — user role label color; mostly compatibility.229 - `user` — user role label color; mostly compatibility.
230 - `user_bubble_bg` — background behind user message rows.230 - `user_bubble_bg` — background behind user message rows.
231 - `user_bubble_fg` — foreground inside user message rows.231 - `user_bubble_fg` — foreground inside user message rows.
232-- `assistant` — assistant/zot accent and spinner text.232+- `assistant` — assistant/zut accent and spinner text.
233 - `tool` — tool names, success marks, diff additions.233 - `tool` — tool names, success marks, diff additions.
234 - `tool_out` — plain tool-output text.234 - `tool_out` — plain tool-output text.
235 - `error` — errors, refused calls, diff deletions.235 - `error` — errors, refused calls, diff deletions.
@@ -249,7 +249,7 @@ Spinner settings can appear at top level, under `colors`, or under
249 249
250 - `spinner_frames` — list of frame strings. Single-cell glyphs keep250 - `spinner_frames` — list of frame strings. Single-cell glyphs keep
251 status-bar alignment clean.251 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.
253 - `spinner_interval_ms` — frame interval in milliseconds. Missing or253 - `spinner_interval_ms` — frame interval in milliseconds. Missing or
254 invalid falls back to 80ms.254 invalid falls back to 80ms.
255 255
@@ -292,7 +292,7 @@ subprocess, or executable is required when the extension contains a
292 valid theme file.292 valid theme file.
293 293
294 ```text294 ```text
295-$ZOT_HOME/extensions/my-theme-extension/295+$ZUT_HOME/extensions/my-theme-extension/
296 ├── extension.json296 ├── extension.json
297 └── theme.json297 └── theme.json
298 ```298 ```
@@ -303,17 +303,17 @@ $ZOT_HOME/extensions/my-theme-extension/
303 {303 {
304 "name": "my-theme-extension",304 "name": "my-theme-extension",
305 "version": "1.0.0",305 "version": "1.0.0",
306- "description": "Ships a zot color theme",306+ "description": "Ships a zut color theme",
307 "enabled": true307 "enabled": true
308 }308 }
309 ```309 ```
310 310
311 No `exec` is needed when `theme.json` or `themes/theme.json` exists.311 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.
313 313
314 ## Validate314 ## Validate
315 315
316-zot theme files are plain JSON, not JSONC. Validate before installing:316+zut theme files are plain JSON, not JSONC. Validate before installing:
317 317
318 ```bash318 ```bash
319 python3 -m json.tool theme.json >/dev/null319 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 agents1+# zutfile agents
2 2
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.
4 4
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.
6 6
7 ## Quick start7 ## Quick start
8 8
@@ -20,12 +20,12 @@ Add a minimal `manifest.json`:
20 20
21 ```json21 ```json
22 {22 {
23- "zotfile": 1,23+ "zutfile": 1,
24 "name": "code-reviewer",24 "name": "code-reviewer",
25 "version": "0.1.0",25 "version": "0.1.0",
26 "description": "Reviews a repository and reports actionable findings.",26 "description": "Reviews a repository and reports actionable findings.",
27 "runtime": {27 "runtime": {
28- "min_zot": "0.2.76"28+ "min_zut": "0.2.76"
29 },29 },
30 "model": {30 "model": {
31 "requires": ["tools"],31 "requires": ["tools"],
@@ -65,20 +65,20 @@ summarize files that have no findings.
65 Test the directory directly during development:65 Test the directory directly during development:
66 66
67 ```bash67 ```bash
68-zot inspect ./reviewer68+zut inspect ./reviewer
69-zot run ./reviewer69+zut run ./reviewer
70-zot run ./reviewer "Review the authentication package"70+zut run ./reviewer "Review the authentication package"
71 ```71 ```
72 72
73 Package it when ready:73 Package it when ready:
74 74
75 ```bash75 ```bash
76-zot pack ./reviewer76+zut pack ./reviewer
77-# wrote code-reviewer.zot77+# wrote code-reviewer.zut
78 # digest sha256:...78 # digest sha256:...
79 79
80-zot verify ./code-reviewer.zot80+zut verify ./code-reviewer.zut
81-zot run ./code-reviewer.zot81+zut run ./code-reviewer.zut
82 ```82 ```
83 83
84 The first run displays the declared permissions and asks for consent before the agent starts.84 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
99 99
100 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.100 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.
101 101
102-Symlinks are not supported and cause `zot pack` to fail.102+Symlinks are not supported and cause `zut pack` to fail.
103 103
104 ## `AGENT.md`104 ## `AGENT.md`
105 105
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.
107 107
108 Keep capability and security declarations out of this file. Permissions come only from `manifest.json` and are enforced independently of agent-authored prose.108 Keep capability and security declarations out of this file. Permissions come only from `manifest.json` and are enforced independently of agent-authored prose.
109 109
@@ -115,11 +115,11 @@ Set `replace_system_prompt` to `true` in the manifest to use `AGENT.md` as the r
115 }115 }
116 ```116 ```
117 117
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.
119 119
120 ## Bundled skills120 ## Bundled skills
121 121
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:
123 123
124 ```text124 ```text
125 skills/125 skills/
@@ -141,7 +141,7 @@ description: Diagnose a failing command from its output and relevant source file
141 4. Propose the smallest correction and validation plan.141 4. Propose the smallest correction and validation plan.
142 ```142 ```
143 143
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.
145 145
146 ## Manifest reference146 ## Manifest reference
147 147
@@ -149,13 +149,13 @@ The current manifest shape is:
149 149
150 ```json150 ```json
151 {151 {
152- "zotfile": 1,152+ "zutfile": 1,
153 "name": "code-reviewer",153 "name": "code-reviewer",
154 "version": "0.1.0",154 "version": "0.1.0",
155 "description": "Reviews a repository and reports actionable findings.",155 "description": "Reviews a repository and reports actionable findings.",
156 "license": "MIT",156 "license": "MIT",
157 "runtime": {157 "runtime": {
158- "min_zot": "0.2.76"158+ "min_zut": "0.2.76"
159 },159 },
160 "model": {160 "model": {
161 "requires": ["tools", "reasoning"],161 "requires": ["tools", "reasoning"],
@@ -195,12 +195,12 @@ The current manifest shape is:
195 195
196 | Field | Required | Current behavior |196 | Field | Required | Current behavior |
197 |---|---:|---|197 |---|---:|---|
198-| `zotfile` | yes | Format version. Must be `1`. |198+| `zutfile` | yes | Format version. Must be `1`. |
199 | `name` | yes | Local agent identity and session/data namespace. See naming rules below. |199 | `name` | yes | Local agent identity and session/data namespace. See naming rules below. |
200 | `version` | no | Displayed in consent and inspection output. Semver is recommended but not currently validated. |200 | `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`. |
202 | `license` | no | Package metadata. Not otherwise interpreted by the local runtime. |202 | `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. |
204 | `model` | no | Model capabilities, minimum context, and preferences. |204 | `model` | no | Model capabilities, minimum context, and preferences. |
205 | `permissions` | no | Filesystem and bash permission ceiling. Omitted scopes deny access. |205 | `permissions` | no | Filesystem and bash permission ceiling. Omitted scopes deny access. |
206 | `requirements` | no | Required operating systems and executables. |206 | `requirements` | no | Required operating systems and executables. |
@@ -244,7 +244,7 @@ Unknown capabilities are rejected. Use `tools`, not the draft spelling `tool_cal
244 1. An explicitly selected model, if present, must satisfy the requirements.244 1. An explicitly selected model, if present, must satisfy the requirements.
245 2. The user's configured default is retained if it satisfies them.245 2. The user's configured default is retained if it satisfies them.
246 3. The first compatible model in `preferred` is selected.246 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.
248 5. If none qualifies, execution stops with an error.248 5. If none qualifies, execution stops with an error.
249 249
250 `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.250 `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`:
271 }271 }
272 ```272 ```
273 273
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.
275 275
276 ### Entry fields276 ### Entry fields
277 277
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.
279 279
280 ```json280 ```json
281 {281 {
@@ -310,7 +310,7 @@ Two variables are available in filesystem scopes:
310 | Variable | Resolves to |310 | Variable | Resolves to |
311 |---|---|311 |---|---|
312 | `${workspace}` | The current working directory used for the run. |312 | `${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/`. |
314 314
315 Relative paths are resolved beneath `${workspace}`. For example:315 Relative paths are resolved beneath `${workspace}`. For example:
316 316
@@ -412,7 +412,7 @@ In the current local runtime, `ask` is launch-time capability consent. It does n
412 }412 }
413 ```413 ```
414 414
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.
416 416
417 The allowlist rejects shell substitution, newlines, redirection, backticks, `$()`, `<`, and `>`. This intentionally supports simple command pipelines rather than arbitrary shell programs.417 The allowlist rejects shell substitution, newlines, redirection, backticks, `$()`, `<`, and `>`. This intentionally supports simple command pipelines rather than arbitrary shell programs.
418 418
@@ -439,7 +439,7 @@ Bundled executable extensions are rejected for the same reason. Network filterin
439 439
440 ## Consent and persistent data440 ## Consent and persistent data
441 441
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:
443 443
444 ```text444 ```text
445 Agent code-reviewer@0.1.0 wants to run.445 Agent code-reviewer@0.1.0 wants to run.
@@ -454,7 +454,7 @@ Allow? [y/N]
454 For modes other than `bash: ask`, approval is cached for the exact artifact digest under:454 For modes other than `bash: ask`, approval is cached for the exact artifact digest under:
455 455
456 ```text456 ```text
457-$ZOT_HOME/agents/<name>/consents/<digest>.json457+$ZUT_HOME/agents/<name>/consents/<digest>.json
458 ```458 ```
459 459
460 Any change to the packaged artifact produces a different digest and requires consent again. `bash: ask` always requires fresh launch consent.460 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
462 Non-interactive runs refuse to bypass consent by default. For controlled automation, pass `-y` / `--yes` or set:462 Non-interactive runs refuse to bypass consent by default. For controlled automation, pass `-y` / `--yes` or set:
463 463
464 ```bash464 ```bash
465-zot run -y ./code-reviewer.zot --print "Review this repository"465+zut run -y ./code-reviewer.zut --print "Review this repository"
466 ```466 ```
467 467
468 ```bash468 ```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"
470 ```470 ```
471 471
472 This environment variable skips the consent prompt. Only use it after independently inspecting and trusting the exact artifact being run.472 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
474 Persistent agent data lives under:474 Persistent agent data lives under:
475 475
476 ```text476 ```text
477-$ZOT_HOME/agents/<name>/data/477+$ZUT_HOME/agents/<name>/data/
478 ```478 ```
479 479
480 The directory is created for every run, but the agent can access it only when `${agent_data}` appears in the relevant filesystem permission scope.480 The directory is created for every run, but the agent can access it only when `${agent_data}` appears in the relevant filesystem permission scope.
481 481
482 ## Agent-scoped sessions482 ## Agent-scoped sessions
483 483
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:
485 485
486 ```text486 ```text
487-$ZOT_HOME/sessions/agents/<name>/487+$ZUT_HOME/sessions/agents/<name>/
488 ```488 ```
489 489
490 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.490 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.
491 491
492 ## Commands492 ## Commands
493 493
494-### `zot run`494+### `zut run`
495 495
496 Run an unpackaged directory during development:496 Run an unpackaged directory during development:
497 497
498 ```bash498 ```bash
499-zot run ./my-agent499+zut run ./my-agent
500-zot run -y ./my-agent500+zut run -y ./my-agent
501-zot run ./my-agent "Do the task"501+zut run ./my-agent "Do the task"
502-zot run ./my-agent -y --print "Do the task"502+zut run ./my-agent -y --print "Do the task"
503 ```503 ```
504 504
505 Run a packed artifact:505 Run a packed artifact:
506 506
507 ```bash507 ```bash
508-zot run ./my-agent.zot508+zut run ./my-agent.zut
509-zot run ./my-agent.zot --print "Do the task"509+zut run ./my-agent.zut --print "Do the task"
510-zot run ./my-agent.zot --stream "Do the task"510+zut run ./my-agent.zut --stream "Do the task"
511-zot run ./my-agent.zot --json "Do the task"511+zut run ./my-agent.zut --json "Do the task"
512 ```512 ```
513 513
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.
515 515
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:
517 517
518 ```bash518 ```bash
519-zot run my-agent519+zut run my-agent
520 ```520 ```
521 521
522 A two-part name is treated as a GitHub `owner/repository` reference. Resolution remains local-first, then uses the repository root:522 A two-part name is treated as a GitHub `owner/repository` reference. Resolution remains local-first, then uses the repository root:
523 523
524 ```bash524 ```bash
525-zot run frkr/zot-archify525+zut run frkr/zot-archify
526 ```526 ```
527 527
528 Use three or more parts to select an agent directory from any public GitHub collection:528 Use three or more parts to select an agent directory from any public GitHub collection:
529 529
530 ```bash530 ```bash
531-zot run acme/agents/reviewer531+zut run acme/agents/reviewer
532-zot run acme/clankers/reviewers/go532+zut run acme/clankers/reviewers/go
533 ```533 ```
534 534
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.
536 536
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:
538 538
539 ```bash539 ```bash
540-zot run https://github.com/acme/agents/reviewer540+zut run https://github.com/acme/agents/reviewer
541 ```541 ```
542 542
543 A standard GitHub tree URL is also supported:543 A standard GitHub tree URL is also supported:
544 544
545 ```bash545 ```bash
546-zot run https://github.com/acme/agents/tree/main/reviewer546+zut run https://github.com/acme/agents/tree/main/reviewer
547 ```547 ```
548 548
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`.
550 550
551 `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.551 `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.
552 552
553-### `zot pack`553+### `zut pack`
554 554
555 ```bash555 ```bash
556-zot pack [directory] [output]556+zut pack [directory] [output]
557 ```557 ```
558 558
559 Examples:559 Examples:
560 560
561 ```bash561 ```bash
562-zot pack562+zut pack
563-zot pack ./my-agent563+zut pack ./my-agent
564-zot pack ./my-agent ./dist/reviewer.zot564+zut pack ./my-agent ./dist/reviewer.zut
565 ```565 ```
566 566
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.
568 568
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.
570 570
571-### `zot inspect`571+### `zut inspect`
572 572
573 ```bash573 ```bash
574-zot inspect ./my-agent574+zut inspect ./my-agent
575-zot inspect ./my-agent.zot575+zut inspect ./my-agent.zut
576-zot inspect https://github.com/acme/agents/code-reviewer576+zut inspect https://github.com/acme/agents/code-reviewer
577 ```577 ```
578 578
579 Inspection validates and prints the agent's name, version, description, digest, declared permissions, and complete file list. It does not execute the agent.579 Inspection validates and prints the agent's name, version, description, digest, declared permissions, and complete file list. It does not execute the agent.
580 580
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.
582 582
583-### `zot verify`583+### `zut verify`
584 584
585 ```bash585 ```bash
586-zot verify ./my-agent.zot586+zut verify ./my-agent.zut
587 ```587 ```
588 588
589 The local `verify` command validates that the archive can be safely loaded, validates the manifest and required layout, and prints its SHA-256 digest.589 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:
596 596
597 | Limit | Value |597 | Limit | Value |
598 |---|---:|598 |---|---:|
599-| Compressed `.zot` file | 100 MiB |599+| Compressed `.zut` file | 100 MiB |
600 | One extracted entry | 64 MiB |600 | One extracted entry | 64 MiB |
601 | Total extracted content | 256 MiB |601 | Total extracted content | 256 MiB |
602 602
@@ -607,7 +607,7 @@ Archive extraction rejects absolute paths and parent traversal. Unsupported tar
607 1. Start with the narrowest permissions possible. A reviewer usually needs repository reads, not writes or bash.607 1. Start with the narrowest permissions possible. A reviewer usually needs repository reads, not writes or bash.
608 2. Keep `AGENT.md` focused on stable behavior. Put task-specific procedures in skills so the model loads them only when relevant.608 2. Keep `AGENT.md` focused on stable behavior. Put task-specific procedures in skills so the model loads them only when relevant.
609 3. Declare every external command in both `requirements.bin` and the bash allowlist when the agent must execute it.609 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`.
611 5. Test denied behavior as well as allowed behavior. Ask the agent to attempt an out-of-scope read, write, and command.611 5. Test denied behavior as well as allowed behavior. Ask the agent to attempt an out-of-scope read, write, and command.
612 6. Pack the same source twice and compare behavior and output digests before distributing it.612 6. Pack the same source twice and compare behavior and output digests before distributing it.
613 7. Inspect and verify the final archive, not only its source directory.613 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
617 617
618 Implemented now:618 Implemented now:
619 619
620-- local directories and `.zot` archives620+- local directories and `.zut` archives
621 - local-only resolution for single-part names621 - local-only resolution for single-part names
622 - local-first `owner/repository` and `owner/repository/path` resolution to any public GitHub repository622 - local-first `owner/repository` and `owner/repository/path` resolution to any public GitHub repository
623 - temporary execution of agent directories from public GitHub repositories623 - 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`
625 - canonical tar creation with zstd compression625 - canonical tar creation with zstd compression
626 - archive digest reporting and safe extraction limits626 - archive digest reporting and safe extraction limits
627 - `AGENT.md` append or replacement behavior627 - `AGENT.md` append or replacement behavior
@@ -636,8 +636,8 @@ Implemented now:
636 636
637 Not implemented yet:637 Not implemented yet:
638 638
639-- `zot install`, `zot agents`, `zot use`, `zot update`, or `zot publish`639+- `zut install`, `zut agents`, `zut use`, `zut update`, or `zut publish`
640-- installed-name, arbitrary URL, OCI, configurable registry, or zot.sh index resolution640+- installed-name, arbitrary URL, OCI, configurable registry, or GitHub index resolution
641 - network allowlist enforcement641 - network allowlist enforcement
642 - environment-variable filtering642 - environment-variable filtering
643 - safely confined bundled executable extensions643 - safely confined bundled executable extensions
@@ -646,4 +646,4 @@ Not implemented yet:
646 - per-agent Telegram, bot, or RPC selection646 - per-agent Telegram, bot, or RPC selection
647 - `--no-sandbox`, `--trust-bash`, or registry trust override flags647 - `--no-sandbox`, `--trust-bash`, or registry trust override flags
648 648
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 extension1+# approve — example zut extension
2 2
3 Demonstrates the **spontaneous `open_panel`** pattern: an3 Demonstrates the **spontaneous `open_panel`** pattern: an
4 extension-registered tool opens a panel from inside its handler4 extension-registered tool opens a panel from inside its handler
@@ -39,12 +39,12 @@ go build -o approve .
39 ## Install39 ## Install
40 40
41 ```bash41 ```bash
42-zot ext install .42+zut ext install .
43 ```43 ```
44 44
45 ## Try it45 ## Try it
46 46
47-In zot, ask:47+In zut, ask:
48 48
49 > Request approval to delete the temp directory.49 > Request approval to delete the temp directory.
50 50
M examples/extensions/approve/main.go +3 -3
@@ -16,9 +16,9 @@
16 //16 //
17 // Install:17 // Install:
18 //18 //
19-// zot ext install .19+// zut ext install .
20 //20 //
21-// Try it — ask zot something like:21+// Try it — ask zut something like:
22 //22 //
23 // "Request approval to delete the temp directory."23 // "Request approval to delete the temp directory."
24 //24 //
@@ -35,7 +35,7 @@ import (
35 "strings"35 "strings"
36 "sync"36 "sync"
37 37
38- "github.com/patriceckhart/zot/packages/agent/ext"38+ "github.com/bnema/zut/packages/agent/ext"
39 )39 )
40 40
41 const schema = `{41 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)
2 2
3 A minimal TypeScript-style extension showing the wire protocol from3 A minimal TypeScript-style extension showing the wire protocol from
4 the Node side without any SDK. Pure stdlib (`readline`, `process`).4 the Node side without any SDK. Pure stdlib (`readline`, `process`).
@@ -12,15 +12,15 @@ Node 18 or newer (uses ESM). No `npm install` step.
12 From this directory:12 From this directory:
13 13
14 ```bash14 ```bash
15-zot ext install .15+zut ext install .
16 ```16 ```
17 17
18-This copies the manifest + script into `$ZOT_HOME/extensions/clock/`.18+This copies the manifest + script into `$ZUT_HOME/extensions/clock/`.
19-zot picks it up the next time you launch the TUI.19+zut picks it up the next time you launch the TUI.
20 20
21 ## Use21 ## Use
22 22
23-In zot:23+In zut:
24 24
25 - `/now` — extension pushes a styled note showing local25 - `/now` — extension pushes a styled note showing local
26 and ISO time (no model call)26 and ISO time (no model call)
@@ -44,7 +44,7 @@ authoring works too — rename `index.js` → `index.ts`, install
44 }44 }
45 ```45 ```
46 46
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`
48 points at and reads/writes JSON lines on its stdio.48 points at and reads/writes JSON lines on its stdio.
49 49
50 ## See also50 ## 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).
2 //2 //
3 // Registers two slash commands:3 // Registers two slash commands:
4 // /now — pushes the current local time into the chat as4 // /now — pushes the current local time into the chat as
@@ -15,9 +15,9 @@
15 // stdout.15 // stdout.
16 //16 //
17 // Install:17 // Install:
18-// zot ext install /path/to/this/dir18+// zut ext install /path/to/this/dir
19 //19 //
20-// Then in zot:20+// Then in zut:
21 // /now21 // /now
22 // /uptime22 // /uptime
23 23
@@ -31,7 +31,7 @@ const STARTED_AT = Date.now();
31 /** @typedef {{type: string, id?: string, [k: string]: unknown}} Frame */31 /** @typedef {{type: string, id?: string, [k: string]: unknown}} Frame */
32 32
33 /**33 /**
34- * Send a frame to zot. One JSON object per line; flush immediately34+ * Send a frame to zut. One JSON object per line; flush immediately
35 * so the host doesn't sit waiting on a buffer.35 * so the host doesn't sit waiting on a buffer.
36 * @param {Frame} obj36 * @param {Frame} obj
37 */37 */
@@ -40,7 +40,7 @@ function send(obj) {
40 }40 }
41 41
42 /**42 /**
43- * stderr is captured by zot to $ZOT_HOME/logs/ext-clock.log; perfect43+ * stderr is captured by zut to $ZUT_HOME/logs/ext-clock.log; perfect
44 * for debug output. Anything written to stdout would corrupt the44 * for debug output. Anything written to stdout would corrupt the
45 * protocol stream.45 * protocol stream.
46 * @param {string} msg46 * @param {string} msg
@@ -52,6 +52,7 @@ function log(msg) {
52 // 1. Hello first.52 // 1. Hello first.
53 send({53 send({
54 type: "hello",54 type: "hello",
55+ protocol_version: 2,
55 name: NAME,56 name: NAME,
56 version: VERSION,57 version: VERSION,
57 capabilities: ["commands"],58 capabilities: ["commands"],
@@ -69,7 +70,7 @@ send({
69 description: "ask the agent to riff on how long the clock ext has run",70 description: "ask the agent to riff on how long the clock ext has run",
70 });71 });
71 72
72-// 3. Read frames until stdin closes (zot shuts us down).73+// 3. Read frames until stdin closes (zut shuts us down).
73 const rl = createInterface({ input: stdin, crlfDelay: Infinity });74 const rl = createInterface({ input: stdin, crlfDelay: Infinity });
74 75
75 rl.on("line", (line) => {76 rl.on("line", (line) => {
@@ -84,8 +85,14 @@ rl.on("line", (line) => {
84 85
85 switch (frame.type) {86 switch (frame.type) {
86 case "hello_ack":87 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+ }
87 log(94 log(
88- `connected to zot ${frame.zot_version} (${frame.provider}/${frame.model})`,95+ `connected to zut ${frame.zut_version} (${frame.provider}/${frame.model})`,
89 );96 );
90 break;97 break;
91 98
@@ -147,7 +154,7 @@ function handleCommand(frame) {
147 id,154 id,
148 action: "prompt",155 action: "prompt",
149 prompt:156 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. ` +
151 `Riff on that briefly in one short sentence — be a little dramatic. ${focus}`.trim(),158 `Riff on that briefly in one short sentence — be a little dramatic. ${focus}`.trim(),
152 });159 });
153 return;160 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)
2 2
3 Demonstrates the **event subscription** and **tool-call interception**3 Demonstrates the **event subscription** and **tool-call interception**
4 half of the extension protocol (phase 3).4 half of the extension protocol (phase 3).
@@ -6,7 +6,7 @@ half of the extension protocol (phase 3).
6 What it does:6 What it does:
7 7
8 - Subscribes to `session_start`, `turn_start`, `tool_call`, `turn_end`8 - 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.
10 - Intercepts every `bash` tool call. If the command matches a danger10 - Intercepts every `bash` tool call. If the command matches a danger
11 regex (`rm -rf`, `sudo`, `dd of=/`, `mkfs`, the fork bomb, `chmod -R11 regex (`rm -rf`, `sudo`, `dd of=/`, `mkfs`, the fork bomb, `chmod -R
12 777`), the call is **refused**. The model sees the refusal as the12 777`), the call is **refused**. The model sees the refusal as the
@@ -23,12 +23,12 @@ go build -o guard .
23 ## Install23 ## Install
24 24
25 ```bash25 ```bash
26-zot ext install .26+zut ext install .
27 ```27 ```
28 28
29 ## Try it29 ## Try it
30 30
31-In zot, ask:31+In zut, ask:
32 32
33 > Run `rm -rf /tmp/foo`33 > Run `rm -rf /tmp/foo`
34 34
@@ -42,7 +42,7 @@ Allowed; the audit log records the call.
42 Tail the audit log:42 Tail the audit log:
43 43
44 ```bash44 ```bash
45-tail -f /tmp/zot-guard-audit.log45+tail -f /tmp/zut-guard-audit.log
46 ```46 ```
47 47
48 ## Extending the danger list48 ## Extending the danger list
M examples/extensions/guard/main.go +4 -4
@@ -15,8 +15,8 @@
15 // cd examples/extensions/guard15 // cd examples/extensions/guard
16 // go build -o guard .16 // go build -o guard .
17 //17 //
18-// Drop it next to its extension.json under $ZOT_HOME/extensions/guard/18+// Drop it next to its extension.json under $ZUT_HOME/extensions/guard/
19-// (or `zot ext install ./guard` from this directory) and the next zot19+// (or `zut ext install ./guard` from this directory) and the next zut
20 // session will load it automatically.20 // session will load it automatically.
21 package main21 package main
22 22
@@ -29,7 +29,7 @@ import (
29 "strings"29 "strings"
30 "time"30 "time"
31 31
32- "github.com/patriceckhart/zot/packages/agent/ext"32+ "github.com/bnema/zut/packages/agent/ext"
33 )33 )
34 34
35 var dangerPatterns = []*regexp.Regexp{35 var dangerPatterns = []*regexp.Regexp{
@@ -44,7 +44,7 @@ var dangerPatterns = []*regexp.Regexp{
44 func main() {44 func main() {
45 e := ext.New("guard", "1.0.0")45 e := ext.New("guard", "1.0.0")
46 46
47- auditPath := filepath.Join(os.TempDir(), "zot-guard-audit.log")47+ auditPath := filepath.Join(os.TempDir(), "zut-guard-audit.log")
48 auditFile, _ := os.OpenFile(auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)48 auditFile, _ := os.OpenFile(auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
49 if auditFile != nil {49 if auditFile != nil {
50 defer auditFile.Close()50 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)
2 2
3 A minimal example showing how to register slash commands with the3 A minimal example showing how to register slash commands with the
4 `ext` SDK.4 `ext` SDK.
@@ -14,16 +14,16 @@ go build -o hello .
14 14
15 ```bash15 ```bash
16 # from this directory16 # from this directory
17-zot ext install .17+zut ext install .
18 ```18 ```
19 19
20 This copies the directory (manifest + binary) into20 This copies the directory (manifest + binary) into
21-`$ZOT_HOME/extensions/hello/`. zot picks it up the next time you21+`$ZUT_HOME/extensions/hello/`. zut picks it up the next time you
22 launch the TUI.22 launch the TUI.
23 23
24 ## Use24 ## Use
25 25
26-In zot, type:26+In zut, type:
27 27
28 - `/hello` — agent greets you28 - `/hello` — agent greets you
29 - `/hello <name>` — agent greets `<name>`29 - `/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.
2 //2 //
3 // Build it:3 // Build it:
4 //4 //
@@ -6,14 +6,14 @@
6 // go build -o hello .6 // go build -o hello .
7 //7 //
8 // Then drop it next to its extension.json under8 // 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`
10 // from this directory.10 // from this directory.
11 package main11 package main
12 12
13 import (13 import (
14 "strings"14 "strings"
15 15
16- "github.com/patriceckhart/zot/packages/agent/ext"16+ "github.com/bnema/zut/packages/agent/ext"
17 )17 )
18 18
19 func main() {19 func main() {
M examples/extensions/mcp-bridge/Makefile +10 -10
@@ -3,13 +3,13 @@
3 BIN := $(notdir $(CURDIR))3 BIN := $(notdir $(CURDIR))
4 # Must match "name" in extension.json (slash command/logical extension name).4 # Must match "name" in extension.json (slash command/logical extension name).
5 EXT := mcp5 EXT := mcp
6-ZOT_HOME ?= $(shell \6+ZUT_HOME ?= $(shell \
7 if [ -n "$$XDG_STATE_HOME" ]; then \7 if [ -n "$$XDG_STATE_HOME" ]; then \
8- printf '%s/zot' "$$XDG_STATE_HOME"; \8+ printf '%s/zut' "$$XDG_STATE_HOME"; \
9 elif [ "$$(uname -s)" = "Darwin" ]; then \9 elif [ "$$(uname -s)" = "Darwin" ]; then \
10- printf '%s/Library/Application Support/zot' "$$HOME"; \10+ printf '%s/Library/Application Support/zut' "$$HOME"; \
11 else \11 else \
12- printf '%s/.local/state/zot' "$$HOME"; \12+ printf '%s/.local/state/zut' "$$HOME"; \
13 fi)13 fi)
14 14
15 default: install15 default: install
@@ -18,11 +18,11 @@ build:
18 go build -o $(BIN) .18 go build -o $(BIN) .
19 19
20 install:20 install:
21- -zot ext remove $(EXT) --yes21+ -zut ext remove $(EXT) --yes
22- rm -rf "$(ZOT_HOME)/extensions/$(BIN)"22+ rm -rf "$(ZUT_HOME)/extensions/$(BIN)"
23- mkdir -p "$(ZOT_HOME)/extensions/$(EXT)"23+ mkdir -p "$(ZUT_HOME)/extensions/$(EXT)"
24- cp extension.json "$(ZOT_HOME)/extensions/$(EXT)/extension.json"24+ cp extension.json "$(ZUT_HOME)/extensions/$(EXT)/extension.json"
25- go build -o "$(ZOT_HOME)/extensions/$(EXT)/$(BIN)" .25+ go build -o "$(ZUT_HOME)/extensions/$(EXT)/$(BIN)" .
26 26
27 uninstall:27 uninstall:
28- -zot ext remove $(EXT) --yes28+ -zut ext remove $(EXT) --yes
M examples/extensions/mcp-bridge/README.md +17 -17
@@ -1,8 +1,8 @@
1 # mcp-bridge1 # mcp-bridge
2 2
3-Connect zot to [MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers.3+Connect zut to [MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers.
4 4
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.
6 6
7 ## Features7 ## Features
8 8
@@ -31,8 +31,8 @@ This extension reads MCP server configurations from standard locations (same for
31 2. **Create a project config file:**31 2. **Create a project config file:**
32 32
33 ```bash33 ```bash
34- mkdir -p .zot34+ mkdir -p .zut
35- cat > .zot/mcp.json << 'EOF'35+ cat > .zut/mcp.json << 'EOF'
36 {36 {
37 "mcpServers": {37 "mcpServers": {
38 "filesystem": {38 "filesystem": {
@@ -51,10 +51,10 @@ This extension reads MCP server configurations from standard locations (same for
51 3. **Install the extension:**51 3. **Install the extension:**
52 52
53 ```bash53 ```bash
54- zot ext install .54+ zut ext install .
55 ```55 ```
56 56
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.
58 58
59 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.59 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.
60 60
@@ -64,12 +64,12 @@ Config files are loaded from two locations (project overrides global per-server)
64 64
65 | Location | Scope |65 | Location | Scope |
66 |---|---|66 |---|---|
67-| `$ZOT_HOME/mcp.json` | Global (`$XDG_STATE_HOME/zot/mcp.json` when `XDG_STATE_HOME` is set) |67+| `$ZUT_HOME/mcp.json` | Global (`$XDG_STATE_HOME/zut/mcp.json` when `XDG_STATE_HOME` is set) |
68-| `.zot/mcp.json` | Project-level (in your project root) |68+| `.zut/mcp.json` | Project-level (in your project root) |
69 69
70 ### Config Format70 ### Config Format
71 71
72-Standard MCP config — same as Claude Desktop, with zot-specific extensions:72+Standard MCP config — same as Claude Desktop, with zut-specific extensions:
73 73
74 ```jsonc74 ```jsonc
75 {75 {
@@ -154,7 +154,7 @@ Standard MCP config — same as Claude Desktop, with zot-specific extensions:
154 154
155 ```155 ```
156 ┌──────────────────────────────────────────────────────────────┐156 ┌──────────────────────────────────────────────────────────────┐
157-│ zot agent │157+│ zut agent │
158 │ │158 │ │
159 │ ┌──────────┐ tool_call ┌──────────────┐ │159 │ ┌──────────┐ tool_call ┌──────────────┐ │
160 │ │ LLM │───────────────▶│ mcp-bridge │ │160 │ │ LLM │───────────────▶│ mcp-bridge │ │
@@ -173,7 +173,7 @@ Standard MCP config — same as Claude Desktop, with zot-specific extensions:
173 173
174 1. **Startup**: mcp-bridge reads config and registers tools from `mcp-tools-cache.json`174 1. **Startup**: mcp-bridge reads config and registers tools from `mcp-tools-cache.json`
175 2. **Background refresh**: starts configured MCP servers, calls `tools/list`, and updates the cache when definitions change175 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 definitions176+3. **Reload**: if the cache changed, run `/reload-ext` once so zut rebuilds the tool registry with the new definitions
177 4. **Naming**: tools appear as `mcp__<server>__<tool>` (e.g., `mcp__filesystem__read_file`)177 4. **Naming**: tools appear as `mcp__<server>__<tool>` (e.g., `mcp__filesystem__read_file`)
178 5. **Idle timeout**: servers not used for 5 minutes are automatically stopped178 5. **Idle timeout**: servers not used for 5 minutes are automatically stopped
179 6. **Auto-respawn**: calling a tool on a stopped server wakes it up179 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:
196 196
197 ## Tool Naming197 ## Tool Naming
198 198
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:
200 200
201 ```201 ```
202 mcp__<server>__<tool>202 mcp__<server>__<tool>
@@ -214,7 +214,7 @@ Server and tool names are sanitized (non-alphanumeric characters become `_`).
214 214
215 The bridge uses a "smart lazy" strategy:215 The bridge uses a "smart lazy" strategy:
216 216
217-1. **On startup**: cached tool definitions are registered without blocking zot startup217+1. **On startup**: cached tool definitions are registered without blocking zut startup
218 2. **In the background**: servers start long enough to refresh the tool cache218 2. **In the background**: servers start long enough to refresh the tool cache
219 3. **During use**: servers stay running for fast tool calls219 3. **During use**: servers stay running for fast tool calls
220 4. **After 5 min idle**: unused servers are automatically stopped (saves memory/CPU)220 4. **After 5 min idle**: unused servers are automatically stopped (saves memory/CPU)
@@ -235,7 +235,7 @@ This gives you:
235 235
236 **View extension logs:**236 **View extension logs:**
237 ```bash237 ```bash
238-zot ext logs mcp -f238+zut ext logs mcp -f
239 ```239 ```
240 240
241 **Common issues:**241 **Common issues:**
@@ -261,11 +261,11 @@ go build -o mcp-bridge .
261 go test ./...261 go test ./...
262 go vet ./...262 go vet ./...
263 263
264-# Run without installing (for one zot session)264+# Run without installing (for one zut session)
265-zot --ext .265+zut --ext .
266 266
267 # View logs267 # View logs
268-zot ext logs mcp -f268+zut ext logs mcp -f
269 ```269 ```
270 270
271 ## License271 ## 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.
2 //2 //
3-// Converts MCP tools into zot-registered tools with namespaced names:3+// Converts MCP tools into zut-registered tools with namespaced names:
4 //4 //
5 // mcp__<server>__<tool>5 // mcp__<server>__<tool>
6 //6 //
7 // The double underscore separates the server name from the tool name,7 // 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).
9 package main9 package main
10 10
11 import (11 import (
@@ -20,8 +20,8 @@ import (
20 "sync"20 "sync"
21 "time"21 "time"
22 22
23+ "github.com/bnema/zut/packages/agent/ext"
23 "github.com/mark3labs/mcp-go/mcp"24 "github.com/mark3labs/mcp-go/mcp"
24- "github.com/patriceckhart/zot/packages/agent/ext"
25 )25 )
26 26
27 const (27 const (
@@ -48,14 +48,14 @@ var mcpSearchToolSchema = json.RawMessage(`{
48 "additionalProperties": false48 "additionalProperties": false
49 }`)49 }`)
50 50
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.
52 type toolMapping struct {52 type toolMapping struct {
53 serverName string // e.g. "filesystem"53 serverName string // e.g. "filesystem"
54 mcpTool string // e.g. "read_file"54 mcpTool string // e.g. "read_file"
55 description string55 description string
56 }56 }
57 57
58-// bridge connects MCP servers to zot's extension protocol.58+// bridge connects MCP servers to zut's extension protocol.
59 type bridge struct {59 type bridge struct {
60 e *ext.Extension60 e *ext.Extension
61 cwd string61 cwd string
@@ -64,14 +64,14 @@ type bridge struct {
64 // the lock-free reads in handleToolCall and the idle reaper safe.64 // the lock-free reads in handleToolCall and the idle reaper safe.
65 // Do not add or remove entries after startup.65 // Do not add or remove entries after startup.
66 servers map[string]*managedServer66 servers map[string]*managedServer
67- mapping map[string]toolMapping // zot tool name → MCP server + tool; guarded by mu67+ mapping map[string]toolMapping // zut tool name → MCP server + tool; guarded by mu
68 logger *log.Logger68 logger *log.Logger
69 69
70 mu sync.Mutex // guards mapping and searchRegistered70 mu sync.Mutex // guards mapping and searchRegistered
71 searchRegistered bool71 searchRegistered bool
72 }72 }
73 73
74-// newBridge creates a new MCP→zot bridge.74+// newBridge creates a new MCP→zut bridge.
75 func newBridge(e *ext.Extension, cwd string, logger *log.Logger) *bridge {75 func newBridge(e *ext.Extension, cwd string, logger *log.Logger) *bridge {
76 return &bridge{76 return &bridge{
77 e: e,77 e: e,
@@ -82,8 +82,8 @@ func newBridge(e *ext.Extension, cwd string, logger *log.Logger) *bridge {
82 }82 }
83 }83 }
84 84
85-// sanitizeName converts a string into a valid zot tool name component.85+// sanitizeName converts a string into a valid zut tool name component.
86-// Zot tool names must match [a-zA-Z][a-zA-Z0-9_]*.86+// Zut tool names must match [a-zA-Z][a-zA-Z0-9_]*.
87 var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9]`)87 var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9]`)
88 88
89 func sanitizeName(s string) string {89 func sanitizeName(s string) string {
@@ -98,7 +98,7 @@ func sanitizeName(s string) string {
98 return s98 return s
99 }99 }
100 100
101-// toolName builds the namespaced zot tool name for an MCP tool.101+// toolName builds the namespaced zut tool name for an MCP tool.
102 func toolName(serverName, mcpToolName string) string {102 func toolName(serverName, mcpToolName string) string {
103 return fmt.Sprintf("mcp__%s__%s", sanitizeName(serverName), sanitizeName(mcpToolName))103 return fmt.Sprintf("mcp__%s__%s", sanitizeName(serverName), sanitizeName(mcpToolName))
104 }104 }
@@ -222,29 +222,29 @@ func (b *bridge) refreshToolCache(ctx context.Context, path string) (bool, error
222 }222 }
223 223
224 func (b *bridge) registerCachedTool(serverName string, tool cachedTool) {224 func (b *bridge) registerCachedTool(serverName string, tool cachedTool) {
225- zotName := toolName(serverName, tool.Name)225+ zutName := toolName(serverName, tool.Name)
226 226
227 b.mu.Lock()227 b.mu.Lock()
228- if existing, exists := b.mapping[zotName]; exists {228+ if existing, exists := b.mapping[zutName]; exists {
229 b.mu.Unlock()229 b.mu.Unlock()
230 if existing.serverName != serverName || existing.mcpTool != tool.Name {230 if existing.serverName != serverName || existing.mcpTool != tool.Name {
231 b.logger.Printf("tool name collision: %s already maps to %s/%s; skipping %s/%s",231 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)
233 }233 }
234 return234 return
235 }235 }
236- b.mapping[zotName] = toolMapping{236+ b.mapping[zutName] = toolMapping{
237 serverName: serverName,237 serverName: serverName,
238 mcpTool: tool.Name,238 mcpTool: tool.Name,
239 description: tool.Description,239 description: tool.Description,
240 }240 }
241 b.mu.Unlock()241 b.mu.Unlock()
242 242
243- b.e.DeferredTool(zotName, tool.Description, json.RawMessage(tool.Schema), func(args json.RawMessage) ext.ToolResult {243+ b.e.DeferredTool(zutName, tool.Description, json.RawMessage(tool.Schema), func(args json.RawMessage) ext.ToolResult {
244- return b.handleToolCall(zotName, args)244+ return b.handleToolCall(zutName, args)
245 })245 })
246 246
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)
248 }248 }
249 249
250 type toolSearchArgs struct {250 type toolSearchArgs struct {
@@ -326,8 +326,8 @@ func (b *bridge) findTools(query string, limit int) []toolSearchMatch {
326 return matches326 return matches
327 }327 }
328 328
329-func scoreToolMatch(terms []string, zotName string, mapping toolMapping) int {329+func scoreToolMatch(terms []string, zutName string, mapping toolMapping) int {
330- name := strings.ToLower(zotName)330+ name := strings.ToLower(zutName)
331 server := strings.ToLower(mapping.serverName)331 server := strings.ToLower(mapping.serverName)
332 mcpName := strings.ToLower(mapping.mcpTool)332 mcpName := strings.ToLower(mapping.mcpTool)
333 description := strings.ToLower(mapping.description)333 description := strings.ToLower(mapping.description)
@@ -408,17 +408,17 @@ func mcpToolSchema(tool mcp.Tool) any {
408 return schema408 return schema
409 }409 }
410 410
411-// handleToolCall routes a zot tool call to the appropriate MCP server.411+// handleToolCall routes a zut tool call to the appropriate MCP server.
412-func (b *bridge) handleToolCall(zotName string, args json.RawMessage) ext.ToolResult {412+func (b *bridge) handleToolCall(zutName string, args json.RawMessage) ext.ToolResult {
413 b.mu.Lock()413 b.mu.Lock()
414- mapping, ok := b.mapping[zotName]414+ mapping, ok := b.mapping[zutName]
415 b.mu.Unlock()415 b.mu.Unlock()
416 416
417 if !ok {417 if !ok {
418 return ext.TextErrorResult(fmt.Sprintf(418 return ext.TextErrorResult(fmt.Sprintf(
419 "Tool '%s' not found. This tool was registered but is no longer available. "+419 "Tool '%s' not found. This tool was registered but is no longer available. "+
420 "The MCP server may have been stopped. Try running '/mcp' to check server status.",420 "The MCP server may have been stopped. Try running '/mcp' to check server status.",
421- zotName))421+ zutName))
422 }422 }
423 423
424 srv, ok := b.servers[mapping.serverName]424 srv, ok := b.servers[mapping.serverName]
@@ -449,8 +449,8 @@ func (b *bridge) handleToolCall(zotName string, args json.RawMessage) ext.ToolRe
449 err, mapping.serverName, mapping.mcpTool, mapping.serverName))449 err, mapping.serverName, mapping.mcpTool, mapping.serverName))
450 }450 }
451 451
452- // Convert MCP result to zot ToolResult452+ // Convert MCP result to zut ToolResult
453- return mcpResultToZot(result)453+ return mcpResultToZut(result)
454 }454 }
455 455
456 // timeoutErrorMessage formats the user-facing text for a deadline error.456 // timeoutErrorMessage formats the user-facing text for a deadline error.
@@ -461,8 +461,8 @@ func timeoutErrorMessage(serverName string, requestTimeout int) string {
461 requestTimeout, serverName)461 requestTimeout, serverName)
462 }462 }
463 463
464-// mcpResultToZot converts an MCP CallToolResult to a zot ToolResult.464+// mcpResultToZut converts an MCP CallToolResult to a zut ToolResult.
465-func mcpResultToZot(result *mcp.CallToolResult) ext.ToolResult {465+func mcpResultToZut(result *mcp.CallToolResult) ext.ToolResult {
466 if result == nil {466 if result == nil {
467 return ext.TextResult("(no result)")467 return ext.TextResult("(no result)")
468 }468 }
@@ -492,7 +492,7 @@ func mcpResultToZot(result *mcp.CallToolResult) ext.ToolResult {
492 return tr492 return tr
493 }493 }
494 494
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.
496 func (b *bridge) startAll(ctx context.Context) error {496 func (b *bridge) startAll(ctx context.Context) error {
497 var wg sync.WaitGroup497 var wg sync.WaitGroup
498 errCh := make(chan error, len(b.servers))498 errCh := make(chan error, len(b.servers))
@@ -523,7 +523,7 @@ func (b *bridge) startAll(ctx context.Context) error {
523 523
524 // startIdleReaper runs a background goroutine that kills idle servers.524 // startIdleReaper runs a background goroutine that kills idle servers.
525 // It has no shutdown mechanism by design: it lives exactly as long as525 // 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.
527 func (b *bridge) startIdleReaper() {527 func (b *bridge) startIdleReaper() {
528 go func() {528 go func() {
529 ticker := time.NewTicker(30 * time.Second)529 ticker := time.NewTicker(30 * time.Second)
M examples/extensions/mcp-bridge/cache.go +1 -1
@@ -32,7 +32,7 @@ type toolCache struct {
32 }32 }
33 33
34 func toolCachePath() string {34 func toolCachePath() string {
35- return filepath.Join(zotHome(), "mcp-tools-cache.json")35+ return filepath.Join(zutHome(), "mcp-tools-cache.json")
36 }36 }
37 37
38 func serverFingerprint(cfg ServerConfig) string {38 func serverFingerprint(cfg ServerConfig) string {
M examples/extensions/mcp-bridge/config.go +11 -11
@@ -3,8 +3,8 @@
3 // Reads standard MCP config files (same format as Claude Desktop, Cursor, etc.)3 // Reads standard MCP config files (same format as Claude Desktop, Cursor, etc.)
4 // from two locations:4 // from two locations:
5 //5 //
6-// 1. Global: $ZOT_HOME/mcp.json6+// 1. Global: $ZUT_HOME/mcp.json
7-// 2. Project: .zot/mcp.json (in the current working directory)7+// 2. Project: .zut/mcp.json (in the current working directory)
8 //8 //
9 // Project config overrides global config per-server (shallow merge).9 // Project config overrides global config per-server (shallow merge).
10 package main10 package main
@@ -42,23 +42,23 @@ type Config struct {
42 MCPServers map[string]ServerConfig `json:"mcpServers"`42 MCPServers map[string]ServerConfig `json:"mcpServers"`
43 }43 }
44 44
45-// zotHome returns the zot state directory.45+// zutHome returns the zut state directory.
46-func zotHome() string {46+func zutHome() string {
47- if h := os.Getenv("ZOT_HOME"); h != "" {47+ if h := os.Getenv("ZUT_HOME"); h != "" {
48 return h48 return h
49 }49 }
50 if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {50 if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {
51- return filepath.Join(xdg, "zot")51+ return filepath.Join(xdg, "zut")
52 }52 }
53 switch runtime.GOOS {53 switch runtime.GOOS {
54 case "darwin":54 case "darwin":
55 home, _ := os.UserHomeDir()55 home, _ := os.UserHomeDir()
56- return filepath.Join(home, "Library", "Application Support", "zot")56+ return filepath.Join(home, "Library", "Application Support", "zut")
57 case "windows":57 case "windows":
58- return filepath.Join(os.Getenv("APPDATA"), "zot")58+ return filepath.Join(os.Getenv("APPDATA"), "zut")
59 default: // linux, freebsd, etc.59 default: // linux, freebsd, etc.
60 home, _ := os.UserHomeDir()60 home, _ := os.UserHomeDir()
61- return filepath.Join(home, ".local", "state", "zot")61+ return filepath.Join(home, ".local", "state", "zut")
62 }62 }
63 }63 }
64 64
@@ -68,14 +68,14 @@ func loadConfig(cwd string) (Config, error) {
68 cfg := Config{MCPServers: make(map[string]ServerConfig)}68 cfg := Config{MCPServers: make(map[string]ServerConfig)}
69 69
70 // 1. Global config70 // 1. Global config
71- globalPath := filepath.Join(zotHome(), "mcp.json")71+ globalPath := filepath.Join(zutHome(), "mcp.json")
72 if err := mergeConfig(&cfg, globalPath); err != nil && !errors.Is(err, fs.ErrNotExist) {72 if err := mergeConfig(&cfg, globalPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
73 return cfg, fmt.Errorf("global config %s: %w", globalPath, err)73 return cfg, fmt.Errorf("global config %s: %w", globalPath, err)
74 }74 }
75 75
76 // 2. Project config (overrides global per-server)76 // 2. Project config (overrides global per-server)
77 if cwd != "" {77 if cwd != "" {
78- projectPath := filepath.Join(cwd, ".zot", "mcp.json")78+ projectPath := filepath.Join(cwd, ".zut", "mcp.json")
79 if err := mergeConfig(&cfg, projectPath); err != nil && !errors.Is(err, fs.ErrNotExist) {79 if err := mergeConfig(&cfg, projectPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
80 return cfg, fmt.Errorf("project config %s: %w", projectPath, err)80 return cfg, fmt.Errorf("project config %s: %w", projectPath, err)
81 }81 }
M examples/extensions/mcp-bridge/config_test.go +4 -4
@@ -19,7 +19,7 @@ func writeJSON(t *testing.T, path, content string) {
19 func TestLoadConfigProjectOverridesGlobal(t *testing.T) {19 func TestLoadConfigProjectOverridesGlobal(t *testing.T) {
20 home := t.TempDir()20 home := t.TempDir()
21 proj := t.TempDir()21 proj := t.TempDir()
22- t.Setenv("ZOT_HOME", home)22+ t.Setenv("ZUT_HOME", home)
23 23
24 writeJSON(t, filepath.Join(home, "mcp.json"), `{24 writeJSON(t, filepath.Join(home, "mcp.json"), `{
25 "mcpServers": {25 "mcpServers": {
@@ -27,7 +27,7 @@ func TestLoadConfigProjectOverridesGlobal(t *testing.T) {
27 "global-only": {"command": "g"}27 "global-only": {"command": "g"}
28 }28 }
29 }`)29 }`)
30- writeJSON(t, filepath.Join(proj, ".zot", "mcp.json"), `{30+ writeJSON(t, filepath.Join(proj, ".zut", "mcp.json"), `{
31 "mcpServers": {31 "mcpServers": {
32 "shared": {"command": "project-cmd"}32 "shared": {"command": "project-cmd"}
33 }33 }
@@ -50,7 +50,7 @@ func TestLoadConfigProjectOverridesGlobal(t *testing.T) {
50 }50 }
51 51
52 func TestLoadConfigMissingFilesIsNotAnError(t *testing.T) {52 func TestLoadConfigMissingFilesIsNotAnError(t *testing.T) {
53- t.Setenv("ZOT_HOME", t.TempDir())53+ t.Setenv("ZUT_HOME", t.TempDir())
54 cfg, err := loadConfig(t.TempDir())54 cfg, err := loadConfig(t.TempDir())
55 if err != nil {55 if err != nil {
56 t.Fatalf("loadConfig with no files: %v", err)56 t.Fatalf("loadConfig with no files: %v", err)
@@ -61,7 +61,7 @@ func TestLoadConfigMissingFilesIsNotAnError(t *testing.T) {
61 }61 }
62 62
63 func TestHandleSetupProjectRequiresCwd(t *testing.T) {63 func TestHandleSetupProjectRequiresCwd(t *testing.T) {
64- t.Setenv("ZOT_HOME", t.TempDir())64+ t.Setenv("ZUT_HOME", t.TempDir())
65 if _, err := handleSetup([]string{"add", "grep", "--project"}, ""); err == nil {65 if _, err := handleSetup([]string{"add", "grep", "--project"}, ""); err == nil {
66 t.Fatal("expected error for --project with unknown working directory")66 t.Fatal("expected error for --project with unknown working directory")
67 }67 }
M examples/extensions/mcp-bridge/deferred_tools_test.go +2 -2
@@ -10,8 +10,8 @@ import (
10 "testing"10 "testing"
11 "time"11 "time"
12 12
13- "github.com/patriceckhart/zot/packages/agent/ext"13+ "github.com/bnema/zut/packages/agent/ext"
14- "github.com/patriceckhart/zot/packages/agent/extproto"14+ "github.com/bnema/zut/packages/agent/extproto"
15 )15 )
16 16
17 type bridgeExtensionHarness struct {17 type bridgeExtensionHarness struct {
M examples/extensions/mcp-bridge/extension.json +1 -1
@@ -3,6 +3,6 @@
3 "version": "1.1.0",3 "version": "1.1.0",
4 "exec": "./mcp-bridge",4 "exec": "./mcp-bridge",
5 "language": "go",5 "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",
7 "enabled": true7 "enabled": true
8 }8 }
M examples/extensions/mcp-bridge/fsutil_test.go +1 -1
@@ -80,7 +80,7 @@ func TestToolCacheVersionMismatchInvalidates(t *testing.T) {
80 80
81 func TestConfigFileWrittenOwnerOnly(t *testing.T) {81 func TestConfigFileWrittenOwnerOnly(t *testing.T) {
82 tmp := t.TempDir()82 tmp := t.TempDir()
83- t.Setenv("ZOT_HOME", tmp)83+ t.Setenv("ZUT_HOME", tmp)
84 84
85 if _, err := handleSetup([]string{"add", "grep"}, tmp); err != nil {85 if _, err := handleSetup([]string{"add", "grep"}, tmp); err != nil {
86 t.Fatalf("handleSetup: %v", err)86 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-bridge1+module github.com/bnema/zut/examples/extensions/mcp-bridge
2 2
3-go 1.25.53+go 1.26
4 4
5-replace github.com/patriceckhart/zot => ../../..5+replace github.com/bnema/zut => ../../..
6 6
7 require (7 require (
8+ github.com/bnema/zut v0.1.0
8 github.com/mark3labs/mcp-go v0.55.19 github.com/mark3labs/mcp-go v0.55.1
9- github.com/patriceckhart/zot v0.2.47
10 )10 )
11 11
12 require (12 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.
2 //2 //
3 // This extension reads MCP server configurations from standard locations3 // This extension reads MCP server configurations from standard locations
4 // (same format as Claude Desktop, Cursor, etc.) and bridges their tools4 // (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.
6 //6 //
7 // Config locations:7 // Config locations:
8-// - Global: $ZOT_HOME/mcp.json8+// - Global: $ZUT_HOME/mcp.json
9-// - Project: .zot/mcp.json9+// - Project: .zut/mcp.json
10 //10 //
11 // Smart lazy: servers are spawned on startup to discover tools, then11 // Smart lazy: servers are spawned on startup to discover tools, then
12 // killed after 5 minutes of idle time. On the next tool call, they're12 // killed after 5 minutes of idle time. On the next tool call, they're
@@ -30,7 +30,7 @@
30 //30 //
31 // Install:31 // Install:
32 //32 //
33-// zot ext install .33+// zut ext install .
34 package main34 package main
35 35
36 import (36 import (
@@ -41,13 +41,13 @@ import (
41 "strings"41 "strings"
42 "time"42 "time"
43 43
44- "github.com/patriceckhart/zot/packages/agent/ext"44+ "github.com/bnema/zut/packages/agent/ext"
45 )45 )
46 46
47 func main() {47 func main() {
48 e := ext.New("mcp", "1.1.0")48 e := ext.New("mcp", "1.1.0")
49 49
50- // Logger writes to stderr (captured by zot into ext logs)50+ // Logger writes to stderr (captured by zut into ext logs)
51 logger := log.New(os.Stderr, "[mcp-bridge] ", log.LstdFlags)51 logger := log.New(os.Stderr, "[mcp-bridge] ", log.LstdFlags)
52 52
53 var b *bridge53 var b *bridge
@@ -105,7 +105,7 @@ func main() {
105 }105 }
106 106
107 func startBackgroundToolRefresh(e *ext.Extension, b *bridge, logger *log.Logger, cachePath string, cachedToolCount int) {107 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 ask108+ // Refresh discovery in the background so zut startup is not blocked. Only ask
109 // for /reload-ext if the discovered tool cache actually changed; otherwise a109 // for /reload-ext if the discovered tool cache actually changed; otherwise a
110 // reload would just repeat this cycle without adding anything.110 // reload would just repeat this cycle without adding anything.
111 go func() {111 go func() {
@@ -216,8 +216,8 @@ func mcpHelp(b *bridge) string {
216 sb.WriteString(" /mcp setup templates List setup templates\n")216 sb.WriteString(" /mcp setup templates List setup templates\n")
217 sb.WriteString(" /mcp setup add <template> [options] Add a server template\n")217 sb.WriteString(" /mcp setup add <template> [options] Add a server template\n")
218 sb.WriteString("\nSetup options\n")218 sb.WriteString("\nSetup options\n")
219- sb.WriteString(" --global Write to $ZOT_HOME/mcp.json (default)\n")219+ sb.WriteString(" --global Write to $ZUT_HOME/mcp.json (default)\n")
220- sb.WriteString(" --project Write to .zot/mcp.json\n")220+ sb.WriteString(" --project Write to .zut/mcp.json\n")
221 sb.WriteString(" --name <server-name> Use a custom configured server name\n")221 sb.WriteString(" --name <server-name> Use a custom configured server name\n")
222 return strings.TrimRight(sb.String(), "\n")222 return strings.TrimRight(sb.String(), "\n")
223 }223 }
M examples/extensions/mcp-bridge/server.go +2 -2
@@ -157,7 +157,7 @@ func (s *managedServer) doStart(ctx context.Context) (*client.Client, []mcp.Tool
157 Params: mcp.InitializeParams{157 Params: mcp.InitializeParams{
158 ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,158 ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
159 ClientInfo: mcp.Implementation{159 ClientInfo: mcp.Implementation{
160- Name: "zot-mcp-bridge",160+ Name: "zut-mcp-bridge",
161 Version: "1.0.0",161 Version: "1.0.0",
162 },162 },
163 Capabilities: mcp.ClientCapabilities{},163 Capabilities: mcp.ClientCapabilities{},
@@ -191,7 +191,7 @@ func (s *managedServer) startStdio() (*client.Client, error) {
191 }191 }
192 192
193 // Create stdio client (this spawns the subprocess). Run stdio MCP193 // Create stdio client (this spawns the subprocess). Run stdio MCP
194- // servers from the zot session's project directory, not the installed194+ // servers from the zut session's project directory, not the installed
195 // extension directory. Many MCP servers use their process cwd as their195 // extension directory. Many MCP servers use their process cwd as their
196 // project root when answering relative-path or directory-listing196 // project root when answering relative-path or directory-listing
197 // requests, so inheriting mcp-bridge's cwd makes them operate on the197 // 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 {
75 75
76 var b strings.Builder76 var b strings.Builder
77 b.WriteString("mcp-bridge setup\n\n")77 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")
79 b.WriteString("Usage:\n")79 b.WriteString("Usage:\n")
80 b.WriteString(" /mcp setup add <template> [--global|--project] [--name <server-name>]\n")80 b.WriteString(" /mcp setup add <template> [--global|--project] [--name <server-name>]\n")
81 b.WriteString(" /mcp setup templates\n\n")81 b.WriteString(" /mcp setup templates\n\n")
@@ -88,7 +88,7 @@ func setupHelp() string {
88 b.WriteString(" /mcp setup add grep\n")88 b.WriteString(" /mcp setup add grep\n")
89 b.WriteString(" /mcp setup add filesystem --project\n")89 b.WriteString(" /mcp setup add filesystem --project\n")
90 b.WriteString(" /mcp setup add context7 --global --name docs\n")90 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")
92 return b.String()92 return b.String()
93 }93 }
94 94
@@ -132,12 +132,12 @@ func handleSetup(args []string, cwd string) (string, error) {
132 }132 }
133 }133 }
134 134
135- path := filepath.Join(zotHome(), "mcp.json")135+ path := filepath.Join(zutHome(), "mcp.json")
136 if target == "project" {136 if target == "project" {
137 if cwd == "" {137 if cwd == "" {
138 return "", fmt.Errorf("--project requires a working directory, but none is known")138 return "", fmt.Errorf("--project requires a working directory, but none is known")
139 }139 }
140- path = filepath.Join(cwd, ".zot", "mcp.json")140+ path = filepath.Join(cwd, ".zut", "mcp.json")
141 }141 }
142 142
143 cfg, err := readConfigFile(path)143 cfg, err := readConfigFile(path)
M examples/extensions/mcp-bridge/setup_test.go +3 -3
@@ -9,7 +9,7 @@ import (
9 9
10 func TestHandleSetupAddGrepGlobal(t *testing.T) {10 func TestHandleSetupAddGrepGlobal(t *testing.T) {
11 tmp := t.TempDir()11 tmp := t.TempDir()
12- t.Setenv("ZOT_HOME", tmp)12+ t.Setenv("ZUT_HOME", tmp)
13 13
14 out, err := handleSetup([]string{"add", "grep"}, tmp)14 out, err := handleSetup([]string{"add", "grep"}, tmp)
15 if err != nil {15 if err != nil {
@@ -44,7 +44,7 @@ func TestHandleSetupAddFilesystemProject(t *testing.T) {
44 t.Fatalf("handleSetup add filesystem: %v", err)44 t.Fatalf("handleSetup add filesystem: %v", err)
45 }45 }
46 46
47- data, err := os.ReadFile(filepath.Join(cwd, ".zot", "mcp.json"))47+ data, err := os.ReadFile(filepath.Join(cwd, ".zut", "mcp.json"))
48 if err != nil {48 if err != nil {
49 t.Fatalf("read project mcp.json: %v", err)49 t.Fatalf("read project mcp.json: %v", err)
50 }50 }
@@ -60,7 +60,7 @@ func TestHandleSetupAddFilesystemProject(t *testing.T) {
60 60
61 func TestHandleSetupDuplicate(t *testing.T) {61 func TestHandleSetupDuplicate(t *testing.T) {
62 tmp := t.TempDir()62 tmp := t.TempDir()
63- t.Setenv("ZOT_HOME", tmp)63+ t.Setenv("ZUT_HOME", tmp)
64 64
65 if _, err := handleSetup([]string{"add", "grep"}, tmp); err != nil {65 if _, err := handleSetup([]string{"add", "grep"}, tmp); err != nil {
66 t.Fatalf("first add failed: %v", err)66 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) {
198 }198 }
199 }199 }
200 200
201-func TestMCPResultToZot(t *testing.T) {201+func TestMCPResultToZut(t *testing.T) {
202- if got := mcpResultToZot(nil); len(got.Content) == 0 || got.IsError {202+ if got := mcpResultToZut(nil); len(got.Content) == 0 || got.IsError {
203 t.Fatalf("nil result: %+v", got)203 t.Fatalf("nil result: %+v", got)
204 }204 }
205 205
@@ -207,7 +207,7 @@ func TestMCPResultToZot(t *testing.T) {
207 Content: []mcp.Content{mcp.TextContent{Type: "text", Text: "hello"}},207 Content: []mcp.Content{mcp.TextContent{Type: "text", Text: "hello"}},
208 IsError: true,208 IsError: true,
209 }209 }
210- got := mcpResultToZot(res)210+ got := mcpResultToZut(res)
211 if !got.IsError {211 if !got.IsError {
212 t.Fatal("IsError not propagated")212 t.Fatal("IsError not propagated")
213 }213 }
@@ -215,7 +215,7 @@ func TestMCPResultToZot(t *testing.T) {
215 t.Fatalf("content count = %d, want 1", len(got.Content))215 t.Fatalf("content count = %d, want 1", len(got.Content))
216 }216 }
217 217
218- empty := mcpResultToZot(&mcp.CallToolResult{})218+ empty := mcpResultToZut(&mcp.CallToolResult{})
219 if len(empty.Content) == 0 {219 if len(empty.Content) == 0 {
220 t.Fatal("empty MCP result must produce placeholder content")220 t.Fatal("empty MCP result must produce placeholder content")
221 }221 }
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)
2 2
3 Real `.ts` (not `.js`), no build step, no SDK. Runs via `npx -y tsx3 Real `.ts` (not `.js`), no build step, no SDK. Runs via `npx -y tsx
4 ./index.ts`, which downloads `tsx` into npm's cache on first4 ./index.ts`, which downloads `tsx` into npm's cache on first
@@ -20,14 +20,14 @@ Node 18+ and `npx` (bundled with npm).
20 From this directory:20 From this directory:
21 21
22 ```bash22 ```bash
23-zot ext install .23+zut ext install .
24 ```24 ```
25 25
26-This copies the manifest + source into `$ZOT_HOME/extensions/scratchpad/`.26+This copies the manifest + source into `$ZUT_HOME/extensions/scratchpad/`.
27 27
28 ## Use28 ## Use
29 29
30-In zot:30+In zut:
31 31
32 - `/note remind me to update the changelog` — appends to the scratchpad32 - `/note remind me to update the changelog` — appends to the scratchpad
33 - `/notes` — shows everything stored33 - `/notes` — shows everything stored
@@ -41,8 +41,8 @@ The model also has a `read_notes` tool. Ask it:
41 41
42 ## Storage42 ## Storage
43 43
44-Notes persist as JSONL at `<cwd>/.zot/scratchpad-notes.jsonl`. The44+Notes persist as JSONL at `<cwd>/.zut/scratchpad-notes.jsonl`. The
45-file is created on first `/note` and survives zot restarts. Each line45+file is created on first `/note` and survives zut restarts. Each line
46 is one note: `{"at":"2026-04-19T13:00:00.000Z","text":"..."}`.46 is one note: `{"at":"2026-04-19T13:00:00.000Z","text":"..."}`.
47 47
48 Scope is per-project: switching to a different cwd gives you a48 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.
2 //2 //
3 // Runs via `npx --yes tsx index.ts` (declared in extension.json).3 // Runs via `npx --yes tsx index.ts` (declared in extension.json).
4 // First invocation downloads tsx into npm's cache; every subsequent4 // First invocation downloads tsx into npm's cache; every subsequent
@@ -47,7 +47,7 @@ interface ToolCall {
47 interface HelloAck {47 interface HelloAck {
48 type: "hello_ack";48 type: "hello_ack";
49 protocol_version: number;49 protocol_version: number;
50- zot_version: string;50+ zut_version: string;
51 provider: string;51 provider: string;
52 model: string;52 model: string;
53 cwd: string;53 cwd: string;
@@ -89,23 +89,23 @@ function send(frame: Frame): void {
89 }89 }
90 90
91 function log(msg: string): void {91 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;
93 // safe for debug output. stdout is reserved for the protocol.93 // safe for debug output. stdout is reserved for the protocol.
94 stderr.write(`[${NAME}] ${msg}\n`);94 stderr.write(`[${NAME}] ${msg}\n`);
95 }95 }
96 96
97 // ---- the scratchpad state itself ----97 // ---- the scratchpad state itself ----
98 //98 //
99-// Notes persist as JSONL under <cwd>/.zot/scratchpad-notes.jsonl so99+// Notes persist as JSONL under <cwd>/.zut/scratchpad-notes.jsonl so
100-// they survive zot restarts and stay scoped to the project. The path100+// they survive zut restarts and stay scoped to the project. The path
101 // is resolved once HelloAck arrives (which carries cwd); until then101 // is resolved once HelloAck arrives (which carries cwd); until then
102 // notesPath is empty and reads/writes no-op safely.102 // notesPath is empty and reads/writes no-op safely.
103 //103 //
104 // One note per line, format: {"at":"<iso>","text":"<body>"}104 // One note per line, format: {"at":"<iso>","text":"<body>"}
105 // Append-only on /note; full rewrite on /clear-notes.105 // Append-only on /note; full rewrite on /clear-notes.
106 //106 //
107-// Single-writer assumption: only one zot session per cwd at a time.107+// Single-writer assumption: only one zut session per cwd at a time.
108-// Concurrent writes from two zot instances would interleave but not108+// Concurrent writes from two zut instances would interleave but not
109 // corrupt JSONL line boundaries on POSIX (writes ≤ PIPE_BUF are109 // corrupt JSONL line boundaries on POSIX (writes ≤ PIPE_BUF are
110 // atomic). Good enough for a demo.110 // atomic). Good enough for a demo.
111 111
@@ -115,7 +115,7 @@ let notes: Note[] = [];
115 let notesPath = "";115 let notesPath = "";
116 116
117 function setNotesPath(cwd: string): void {117 function setNotesPath(cwd: string): void {
118- notesPath = join(cwd, ".zot", "scratchpad-notes.jsonl");118+ notesPath = join(cwd, ".zut", "scratchpad-notes.jsonl");
119 loadNotes();119 loadNotes();
120 }120 }
121 121
@@ -177,6 +177,7 @@ function renderNotes(): string {
177 177
178 send({178 send({
179 type: "hello",179 type: "hello",
180+ protocol_version: 2,
180 name: NAME,181 name: NAME,
181 version: VERSION,182 version: VERSION,
182 capabilities: ["commands", "tools"],183 capabilities: ["commands", "tools"],
@@ -209,7 +210,7 @@ send({
209 },210 },
210 });211 });
211 212
212-// Sentinel: tells zot all initial registrations are flushed so the213+// Sentinel: tells zut all initial registrations are flushed so the
213 // agent's tool registry can be built without racing the read loop.214 // agent's tool registry can be built without racing the read loop.
214 send({ type: "ready" });215 send({ type: "ready" });
215 216
@@ -251,8 +252,14 @@ rl.on("close", () => {
251 });252 });
252 253
253 function handleHelloAck(ack: HelloAck): void {254 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+ }
254 log(261 log(
255- `connected to zot ${ack.zot_version} ` +262+ `connected to zut ${ack.zut_version} ` +
256 `(${ack.provider}/${ack.model}, cwd=${ack.cwd})`,263 `(${ack.provider}/${ack.model}, cwd=${ack.cwd})`,
257 );264 );
258 if (ack.cwd) setNotesPath(ack.cwd);265 if (ack.cwd) setNotesPath(ack.cwd);
M examples/extensions/secret/README.md +3 -3
@@ -1,4 +1,4 @@
1-# secret — example zot extension1+# secret — example zut extension
2 2
3 Demonstrates **secret collection via a masked panel**: the model asks3 Demonstrates **secret collection via a masked panel**: the model asks
4 for a resource that needs a credential, the extension collects the4 for a resource that needs a credential, the extension collects the
@@ -50,12 +50,12 @@ go build -o secret .
50 ## Install50 ## Install
51 51
52 ```bash52 ```bash
53-zot ext install .53+zut ext install .
54 ```54 ```
55 55
56 ## Try it56 ## Try it
57 57
58-In zot, ask:58+In zut, ask:
59 59
60 > Fetch https://internal.example.com/report — it needs a password.60 > Fetch https://internal.example.com/report — it needs a password.
61 61
M examples/extensions/secret/main.go +3 -3
@@ -20,9 +20,9 @@
20 //20 //
21 // Install:21 // Install:
22 //22 //
23-// zot ext install .23+// zut ext install .
24 //24 //
25-// Try it — ask zot something like:25+// Try it — ask zut something like:
26 //26 //
27 // "Fetch https://internal.example.com/report — it needs a password."27 // "Fetch https://internal.example.com/report — it needs a password."
28 //28 //
@@ -38,7 +38,7 @@ import (
38 "strings"38 "strings"
39 "sync"39 "sync"
40 40
41- "github.com/patriceckhart/zot/packages/agent/ext"41+ "github.com/bnema/zut/packages/agent/ext"
42 )42 )
43 43
44 const schema = `{44 const schema = `{
M examples/extensions/tasked-phases/README.md +9 -9
@@ -1,6 +1,6 @@
1 # tasked-phases1 # tasked-phases
2 2
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.
4 It provides:4 It provides:
5 5
6 - the `tasked_phases` tool for persistent spec/phase/task state;6 - the `tasked_phases` tool for persistent spec/phase/task state;
@@ -24,7 +24,7 @@ Successful mutations return one progress line; use `get_status` or
24 24
25 ## Build and run25 ## Build and run
26 26
27-From the zot repository:27+From the zut repository:
28 28
29 ```bash29 ```bash
30 cd examples/extensions/tasked-phases30 cd examples/extensions/tasked-phases
@@ -32,31 +32,31 @@ go build -o tasked-phases .
32 32
33 # One-run development load, launched from the project you want to track:33 # One-run development load, launched from the project you want to track:
34 cd /path/to/project34 cd /path/to/project
35-zot --no-ext --ext /path/to/zot/examples/extensions/tasked-phases35+zut --no-ext --ext /path/to/zut/examples/extensions/tasked-phases
36 36
37 # Or install it globally (run this from the extension directory):37 # Or install it globally (run this from the extension directory):
38-cd /path/to/zot/examples/extensions/tasked-phases38+cd /path/to/zut/examples/extensions/tasked-phases
39-zot ext install --build=go .39+zut ext install --build=go .
40 ```40 ```
41 41
42 `--build=go` explicitly builds the manifest-declared executable in the staged42 `--build=go` explicitly builds the manifest-declared executable in the staged
43 installation. If an incomplete copy was installed already, remove it with43 installation. If an incomplete copy was installed already, remove it with
44-`zot ext remove tasked-phases --yes` before reinstalling. You can also build44+`zut 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 .`.45+manually with `go build -o tasked-phases .` and then use `zut ext install .`.
46 46
47 Then use `/phases` or ask the model to create a spec and phased checklist with47 Then use `/phases` or ask the model to create a spec and phased checklist with
48 `tasked_phases`. The extension keys its durable state by the host-reported48 `tasked_phases`. The extension keys its durable state by the host-reported
49 project CWD.49 project CWD.
50 50
51 The extension bundles its workflow skill under `skills/tasked-phases/`, so51 The extension bundles its workflow skill under `skills/tasked-phases/`, so
52-`zot ext install` makes the guidance available automatically. The standalone52+`zut ext install` makes the guidance available automatically. The standalone
53 copy at `examples/skills/tasked-phases/SKILL.md` remains useful when you want53 copy at `examples/skills/tasked-phases/SKILL.md` remains useful when you want
54 the same workflow without installing the extension.54 the same workflow without installing the extension.
55 55
56 ## Persistence and turn focus56 ## Persistence and turn focus
57 57
58 The extension stores one restrictive state file per project CWD as a fallback58 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 is59+for hosts that do not persist sessions. When zut session persistence is
60 available, the plan is also stored as extension-owned state on each session60 available, the plan is also stored as extension-owned state on each session
61 branch and restored when sessions are opened, switched, or forked.61 branch and restored when sessions are opened, switched, or forked.
62 62
M examples/extensions/tasked-phases/main.go +1 -1
@@ -6,7 +6,7 @@ import (
6 "os"6 "os"
7 "sync"7 "sync"
8 8
9- "github.com/patriceckhart/zot/packages/agent/ext"9+ "github.com/bnema/zut/packages/agent/ext"
10 )10 )
11 11
12 const (12 const (
M examples/extensions/tasked-phases/main_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "strings"4 "strings"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/agent/ext"7+ "github.com/bnema/zut/packages/agent/ext"
8 )8 )
9 9
10 func TestBuildChromeHidesEmptyPlan(t *testing.T) {10 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 {
1043 }1043 }
1044 1044
1045 // activateSession replaces the project fallback with the opaque snapshot1045 // activateSession replaces the project fallback with the opaque snapshot
1046-// supplied by zot for the active session branch. An empty snapshot means the1046+// supplied by zut for the active session branch. An empty snapshot means the
1047 // branch has no extension state yet and therefore starts clean.1047 // branch has no extension state yet and therefore starts clean.
1048 func (s *stateStore) activateSession(raw json.RawMessage) error {1048 func (s *stateStore) activateSession(raw json.RawMessage) error {
1049 s.mu.Lock()1049 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)
2 2
3 Demonstrates the interactive panel API plus a companion tool the model3 Demonstrates the interactive panel API plus a companion tool the model
4 can call. The slash command opens a persistent todo panel; the tool lets4 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.
6 6
7 ## Requirements7 ## Requirements
8 8
@@ -13,7 +13,7 @@ Go 1.22+.
13 From this directory:13 From this directory:
14 14
15 ```bash15 ```bash
16-zot ext install .16+zut ext install .
17 ```17 ```
18 18
19 The example is configured to run directly from source:19 The example is configured to run directly from source:
@@ -45,7 +45,7 @@ If you do build it, change `extension.json` back to:
45 45
46 ## Use46 ## Use
47 47
48-In zot:48+In zut:
49 49
50 - `/todo` opens the panel50 - `/todo` opens the panel
51 51
M examples/extensions/todo/go.mod +4 -4
@@ -1,7 +1,7 @@
1-module zot-todo-extension1+module zut-todo-extension
2 2
3-go 1.223+go 1.26
4 4
5-require github.com/patriceckhart/zot v0.0.05+require github.com/bnema/zut v0.1.0
6 6
7-replace github.com/patriceckhart/zot => /Users/pat/Developer/zot7+replace github.com/bnema/zut => ../../..
M examples/extensions/todo/main.go +1 -1
@@ -8,7 +8,7 @@ import (
8 "strings"8 "strings"
9 "sync"9 "sync"
10 10
11- "github.com/patriceckhart/zot/packages/agent/ext"11+ "github.com/bnema/zut/packages/agent/ext"
12 )12 )
13 13
14 type todoItem struct {14 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)
2 2
3 Demonstrates the **tool registration** half of the extension protocol3 Demonstrates the **tool registration** half of the extension protocol
4 (phase 2). The extension exposes a `weather(city)` tool that the LLM4 (phase 2). The extension exposes a `weather(city)` tool that the LLM
@@ -15,16 +15,16 @@ go build -o weather .
15 ## Install15 ## Install
16 16
17 ```bash17 ```bash
18-zot ext install .18+zut ext install .
19 ```19 ```
20 20
21 Copies the directory (manifest + binary) into21 Copies the directory (manifest + binary) into
22-`$ZOT_HOME/extensions/weather/`. zot picks it up the next time you22+`$ZUT_HOME/extensions/weather/`. zut picks it up the next time you
23 launch the TUI and registers the tool with the agent.23 launch the TUI and registers the tool with the agent.
24 24
25 ## Use25 ## Use
26 26
27-In zot, ask:27+In zut, ask:
28 28
29 - "What's the weather in Berlin?"29 - "What's the weather in Berlin?"
30 - "Compare the weather in Tokyo and Reykjavik."30 - "Compare the weather in Tokyo and Reykjavik."
@@ -35,7 +35,7 @@ anything by hand.
35 35
36 ## Add the leading slash to your /help table36 ## Add the leading slash to your /help table
37 37
38-The tool also shows up in the system prompt's tool list because zot38+The tool also shows up in the system prompt's tool list because zut
39 folds extension tools into the agent's registry at startup.39 folds extension tools into the agent's registry at startup.
40 40
41 ## See also41 ## See also
M examples/extensions/weather/main.go +3 -3
@@ -10,8 +10,8 @@
10 // go build -o weather .10 // go build -o weather .
11 //11 //
12 // Then drop it next to its extension.json under12 // Then drop it next to its extension.json under
13-// $ZOT_HOME/extensions/weather/, or run `zot ext install ./weather`13+// $ZUT_HOME/extensions/weather/, or run `zut ext install ./weather`
14-// from this directory. After that, asking zot anything like14+// from this directory. After that, asking zut anything like
15 // "what's the weather in Berlin?" makes the model call the tool.15 // "what's the weather in Berlin?" makes the model call the tool.
16 package main16 package main
17 17
@@ -21,7 +21,7 @@ import (
21 "fmt"21 "fmt"
22 "strings"22 "strings"
23 23
24- "github.com/patriceckhart/zot/packages/agent/ext"24+ "github.com/bnema/zut/packages/agent/ext"
25 )25 )
26 26
27 const weatherSchema = `{27 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.
2 //2 //
3-// For an in-process Go embedding, use github.com/patriceckhart/zot/packages/agent/sdk3+// For an in-process Go embedding, use github.com/bnema/zut/packages/agent/sdk
4 // instead — this example exists to show what consumers in OTHER4 // instead — this example exists to show what consumers in OTHER
5 // languages have to do.5 // languages have to do.
6 package main6 package main
@@ -22,7 +22,7 @@ func main() {
22 }22 }
23 prompt := strings.Join(os.Args[1:], " ")23 prompt := strings.Join(os.Args[1:], " ")
24 24
25- cmd := exec.Command("zot", "rpc")25+ cmd := exec.Command("zut", "rpc")
26 cmd.Stderr = os.Stderr26 cmd.Stderr = os.Stderr
27 stdin, err := cmd.StdinPipe()27 stdin, err := cmd.StdinPipe()
28 must(err)28 must(err)
@@ -30,7 +30,7 @@ func main() {
30 must(err)30 must(err)
31 must(cmd.Start())31 must(cmd.Start())
32 32
33- if tok := os.Getenv("ZOTCORE_RPC_TOKEN"); tok != "" {33+ if tok := os.Getenv("ZUTCORE_RPC_TOKEN"); tok != "" {
34 send(stdin, map[string]any{"id": "0", "type": "hello", "token": tok})34 send(stdin, map[string]any{"id": "0", "type": "hello", "token": tok})
35 }35 }
36 send(stdin, map[string]any{36 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.
2 //2 //
3 // Usage:3 // Usage:
4-// node zot-client.js "fix the failing test"4+// node zut-client.js "fix the failing test"
5 //5 //
6-// Spawns `zot rpc`, sends one prompt, prints assistant text as it6+// Spawns `zut rpc`, sends one prompt, prints assistant text as it
7 // streams, and exits when the turn finishes. Pure stdlib — no7 // streams, and exits when the turn finishes. Pure stdlib — no
8 // dependencies. See docs/rpc.md for the full schema.8 // dependencies. See docs/rpc.md for the full schema.
9 "use strict";9 "use strict";
@@ -12,9 +12,9 @@ const { spawn } = require("node:child_process");
12 const readline = require("node:readline");12 const readline = require("node:readline");
13 const { randomBytes } = require("node:crypto");13 const { randomBytes } = require("node:crypto");
14 14
15-class ZotClient {15+class ZutClient {
16 constructor(...flags) {16 constructor(...flags) {
17- this.proc = spawn("zot", ["rpc", ...flags], {17+ this.proc = spawn("zut", ["rpc", ...flags], {
18 stdio: ["pipe", "pipe", "inherit"],18 stdio: ["pipe", "pipe", "inherit"],
19 env: process.env,19 env: process.env,
20 });20 });
@@ -52,13 +52,13 @@ class ZotClient {
52 async function main() {52 async function main() {
53 const prompt = process.argv.slice(2).join(" ");53 const prompt = process.argv.slice(2).join(" ");
54 if (!prompt) {54 if (!prompt) {
55- console.error("usage: node zot-client.js <prompt>");55+ console.error("usage: node zut-client.js <prompt>");
56 process.exit(2);56 process.exit(2);
57 }57 }
58 58
59- const client = new ZotClient();59+ const client = new ZutClient();
60 60
61- const token = process.env.ZOTCORE_RPC_TOKEN;61+ const token = process.env.ZUTCORE_RPC_TOKEN;
62 if (token) client.send({ type: "hello", token });62 if (token) client.send({ type: "hello", token });
63 63
64 client.send({ type: "prompt", message: prompt });64 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.
2 2
3 Usage:3 Usage:
4- python zot_client.py "fix the failing test"4+ python zut_client.py "fix the failing test"
5 5
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,
7 and exits when the turn finishes. No external dependencies — stdlib7 and exits when the turn finishes. No external dependencies — stdlib
8 only. Implements just enough of the protocol to be useful as a8 only. Implements just enough of the protocol to be useful as a
9 starting point; see docs/rpc.md for the full schema.9 starting point; see docs/rpc.md for the full schema.
@@ -18,9 +18,9 @@ import threading
18 import uuid18 import uuid
19 19
20 20
21-class ZotClient:21+class ZutClient:
22 def __init__(self, *flags: str) -> None:22 def __init__(self, *flags: str) -> None:
23- argv = ["zot", "rpc", *flags]23+ argv = ["zut", "rpc", *flags]
24 env = os.environ.copy()24 env = os.environ.copy()
25 self.proc = subprocess.Popen(25 self.proc = subprocess.Popen(
26 argv,26 argv,
@@ -67,13 +67,13 @@ class ZotClient:
67 67
68 def main() -> int:68 def main() -> int:
69 if len(sys.argv) < 2:69 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)
71 return 271 return 2
72 prompt = " ".join(sys.argv[1:])72 prompt = " ".join(sys.argv[1:])
73 73
74- client = ZotClient()74+ client = ZutClient()
75 try:75 try:
76- token = os.environ.get("ZOTCORE_RPC_TOKEN")76+ token = os.environ.get("ZUTCORE_RPC_TOKEN")
77 if token:77 if token:
78 client.send(type="hello", token=token)78 client.send(type="hello", token=token)
79 79
M examples/rpc/shell/prompt.sh +6 -6
@@ -1,5 +1,5 @@
1 #!/usr/bin/env bash1 #!/usr/bin/env bash
2-# Minimal shell client for the `zot rpc` JSON protocol.2+# Minimal shell client for the `zut rpc` JSON protocol.
3 #3 #
4 # Usage:4 # Usage:
5 # ./prompt.sh "fix the failing test"5 # ./prompt.sh "fix the failing test"
@@ -18,17 +18,17 @@ prompt="$*"
18 # Build the prompt command frame with jq so quotes are escaped properly.18 # Build the prompt command frame with jq so quotes are escaped properly.
19 cmd=$(jq -nc --arg msg "$prompt" '{id:"1",type:"prompt",message:$msg}')19 cmd=$(jq -nc --arg msg "$prompt" '{id:"1",type:"prompt",message:$msg}')
20 20
21-# Pipe the command into zot rpc; pipe its stdout through jq to react21+# Pipe the command into zut rpc; pipe its stdout through jq to react
22 # to events. The trailing `cat` keeps the input pipe open until the22 # to events. The trailing `cat` keeps the input pipe open until the
23 # subprocess exits on its own (after `done`).23 # subprocess exits on its own (after `done`).
24 {24 {
25- if [ -n "${ZOTCORE_RPC_TOKEN:-}" ]; then25+ if [ -n "${ZUTCORE_RPC_TOKEN:-}" ]; then
26- jq -nc --arg t "$ZOTCORE_RPC_TOKEN" '{id:"0",type:"hello",token:$t}'26+ jq -nc --arg t "$ZUTCORE_RPC_TOKEN" '{id:"0",type:"hello",token:$t}'
27 fi27 fi
28 echo "$cmd"28 echo "$cmd"
29- # Block here so stdin stays open until zot exits.29+ # Block here so stdin stays open until zut exits.
30 cat30 cat
31-} | zot rpc | while IFS= read -r line; do31+} | zut rpc | while IFS= read -r line; do
32 type=$(echo "$line" | jq -r '.type // empty')32 type=$(echo "$line" | jq -r '.type // empty')
33 case "$type" in33 case "$type" in
34 text_delta)34 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.1+// In-process Go embedding of the zut agent runtime via the sdk package.
2-// Compare to examples/rpc/go which spawns `zot rpc` as a subprocess.2+// Compare to examples/rpc/go which spawns `zut rpc` as a subprocess.
3 package main3 package main
4 4
5 import (5 import (
@@ -8,7 +8,7 @@ import (
8 "os"8 "os"
9 "strings"9 "strings"
10 10
11- "github.com/patriceckhart/zot/packages/agent/sdk"11+ "github.com/bnema/zut/packages/agent/sdk"
12 )12 )
13 13
14 func main() {14 func main() {
M examples/themes/custom-spinner.json +1 -1
@@ -1,6 +1,6 @@
1 {1 {
2 "name": "custom-spinner",2 "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.",
4 "colors": {4 "colors": {
5 "dark": {5 "dark": {
6 "spinner_frames": [6 "spinner_frames": [
M examples/themes/vercel.json +2 -2
@@ -1,6 +1,6 @@
1 {1 {
2 "name": "vercel",2 "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.",
4 "color_descriptions": {4 "color_descriptions": {
5 "fg": "Default foreground text, based on Vercel editor.foreground / terminal.foreground.",5 "fg": "Default foreground text, based on Vercel editor.foreground / terminal.foreground.",
6 "muted": "Secondary text, dividers, gutters, footers, inactive hints, based on Vercel foreground/sidebar/status gray.",6 "muted": "Secondary text, dividers, gutters, footers, inactive hints, based on Vercel foreground/sidebar/status gray.",
@@ -8,7 +8,7 @@
8 "user": "User role label color; kept for compatibility with older UI surfaces.",8 "user": "User role label color; kept for compatibility with older UI surfaces.",
9 "user_bubble_bg": "Background behind user message rows, based on Vercel editor.selectionBackground / lineHighlightBackground.",9 "user_bubble_bg": "Background behind user message rows, based on Vercel editor.selectionBackground / lineHighlightBackground.",
10 "user_bubble_fg": "Text foreground inside user message rows.",10 "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.",
12 "tool": "Tool names, successful marks, additions in diffs, based on Vercel green.",12 "tool": "Tool names, successful marks, additions in diffs, based on Vercel green.",
13 "tool_out": "Plain tool output text.",13 "tool_out": "Plain tool output text.",
14 "error": "Errors, refused tool calls, deletions in diffs, based on Vercel error/list error colors.",14 "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/zot1+module github.com/bnema/zut
2 2
3 go 1.263 go 1.26
4 4
M install.ps1 +15 -15
@@ -1,16 +1,16 @@
1-# zot installer for Windows (PowerShell).1+# zut installer for Windows (PowerShell).
2 #2 #
3 # Usage (in PowerShell):3 # Usage (in PowerShell):
4-# iwr -useb https://raw.githubusercontent.com/patriceckhart/zot/main/install.ps1 | iex4+# iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex
5 #5 #
6 # Or with arguments:6 # Or with arguments:
7-# $env:ZOT_VERSION = "v0.0.1"7+# $env:ZUT_VERSION = "v0.1.0"
8-# $env:ZOT_PREFIX = "$HOME\bin"8+# $env:ZUT_PREFIX = "$HOME\bin"
9-# iwr -useb https://raw.githubusercontent.com/patriceckhart/zot/main/install.ps1 | iex9+# iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex
10 #10 #
11 # Detects architecture, downloads the matching .zip from the GitHub11 # Detects architecture, downloads the matching .zip from the GitHub
12-# release, verifies the sha256 against checksums.txt, extracts zot.exe,12+# release, verifies the sha256 against checksums.txt, extracts zut.exe,
13-# and moves it into $ZOT_PREFIX (defaults to $HOME\bin, added to PATH13+# and moves it into $ZUT_PREFIX (defaults to $HOME\bin, added to PATH
14 # via the User environment if missing).14 # via the User environment if missing).
15 #15 #
16 # $env:GITHUB_TOKEN is optional for the public repo. Set it to a PAT16 # $env:GITHUB_TOKEN is optional for the public repo. Set it to a PAT
@@ -21,15 +21,15 @@
21 21
22 [CmdletBinding()]22 [CmdletBinding()]
23 param(23 param(
24- [string]$Version = $env:ZOT_VERSION,24+ [string]$Version = $env:ZUT_VERSION,
25- [string]$Prefix = $env:ZOT_PREFIX25+ [string]$Prefix = $env:ZUT_PREFIX
26 )26 )
27 27
28 $ErrorActionPreference = "Stop"28 $ErrorActionPreference = "Stop"
29 29
30-$owner = "patriceckhart"30+$owner = "bnema"
31-$repo = "zot"31+$repo = "zut"
32-$binary = "zot"32+$binary = "zut"
33 33
34 # Build Authorization header list once; used on every HTTP call so the34 # Build Authorization header list once; used on every HTTP call so the
35 # script works against private repos when $env:GITHUB_TOKEN is set.35 # script works against private repos when $env:GITHUB_TOKEN is set.
@@ -73,7 +73,7 @@ if ($Version -eq "latest") {
73 # GitHub's API wants a User-Agent; Invoke-RestMethod sets one, but be73 # GitHub's API wants a User-Agent; Invoke-RestMethod sets one, but be
74 # explicit so corporate proxies that strip it don't trip a 403.74 # explicit so corporate proxies that strip it don't trip a 403.
75 $apiHeaders = @{} + $headers75 $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" }
77 $apiHeaders["Accept"] = "application/vnd.github+json"77 $apiHeaders["Accept"] = "application/vnd.github+json"
78 78
79 try {79 try {
@@ -106,7 +106,7 @@ $baseUrl = "https://github.com/$owner/$repo/releases/download/$Version"
106 $archiveUrl = "$baseUrl/$archive"106 $archiveUrl = "$baseUrl/$archive"
107 $checksumUrl = "$baseUrl/checksums.txt"107 $checksumUrl = "$baseUrl/checksums.txt"
108 108
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)))
110 110
111 try {111 try {
112 Msg "downloading $archive"112 Msg "downloading $archive"
@@ -152,7 +152,7 @@ try {
152 Warn " `$env:Path = `"$Prefix;`$env:Path`""152 Warn " `$env:Path = `"$Prefix;`$env:Path`""
153 }153 }
154 154
155- Msg "installed. run: zot --help"155+ Msg "installed. run: zut --help"
156 }156 }
157 finally {157 finally {
158 Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue158 Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
M install.sh +15 -15
@@ -1,24 +1,24 @@
1 #!/usr/bin/env bash1 #!/usr/bin/env bash
2 #2 #
3-# zot installer.3+# zut installer.
4 #4 #
5 # Usage:5 # Usage:
6-# curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash6+# curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash
7-# curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash -s -- v0.0.1 ~/bin7+# curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash -s -- v0.1.0 ~/bin
8 #8 #
9 # Positional arguments:9 # 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".
11 # $2 prefix — install directory. Defaults to the first writable11 # $2 prefix — install directory. Defaults to the first writable
12 # directory in: /usr/local/bin, $HOME/.local/bin,12 # directory in: /usr/local/bin, $HOME/.local/bin,
13 # $HOME/bin. Created if missing. Add it to your PATH13 # $HOME/bin. Created if missing. Add it to your PATH
14 # if it isn't already.14 # if it isn't already.
15 #15 #
16 # Environment overrides:16 # Environment overrides:
17-# ZOT_VERSION same as $117+# ZUT_VERSION same as $1
18-# ZOT_PREFIX same as $218+# ZUT_PREFIX same as $2
19 # GITHUB_TOKEN personal access token — required while the repo is19 # GITHUB_TOKEN personal access token — required while the repo is
20 # private, ignored once it goes public. Must have at20 # 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.
22 #22 #
23 # The script detects your OS and architecture, downloads the matching23 # The script detects your OS and architecture, downloads the matching
24 # archive from the GitHub release, verifies the sha256 against the24 # archive from the GitHub release, verifies the sha256 against the
@@ -28,12 +28,12 @@
28 28
29 set -euo pipefail29 set -euo pipefail
30 30
31-OWNER="patriceckhart"31+OWNER="bnema"
32-REPO="zot"32+REPO="zut"
33-BINARY="zot"33+BINARY="zut"
34 34
35-VERSION="${1:-${ZOT_VERSION:-latest}}"35+VERSION="${1:-${ZUT_VERSION:-latest}}"
36-PREFIX="${2:-${ZOT_PREFIX:-}}"36+PREFIX="${2:-${ZUT_PREFIX:-}}"
37 37
38 msg() { printf "\033[1m==>\033[0m %s\n" "$*"; }38 msg() { printf "\033[1m==>\033[0m %s\n" "$*"; }
39 warn() { printf "\033[33mwarn:\033[0m %s\n" "$*" >&2; }39 warn() { printf "\033[33mwarn:\033[0m %s\n" "$*" >&2; }
@@ -175,6 +175,6 @@ case ":$PATH:" in
175 ;;175 ;;
176 esac176 esac
177 177
178-msg "installed $("$PREFIX/$BINARY" --version 2>/dev/null || echo zot)"178+msg "installed $("$PREFIX/$BINARY" --version 2>/dev/null || echo zut)"
179-msg "run: zot (interactive tui)"179+msg "run: zut (interactive tui)"
180-msg "run: zot --help (all flags and subcommands)"180+msg "run: zut --help (all flags and subcommands)"
M packages/agent/args.go +34 -34
@@ -6,8 +6,8 @@ import (
6 "strconv"6 "strconv"
7 "strings"7 "strings"
8 8
9- "github.com/patriceckhart/zot/packages/agent/tools"9+ "github.com/bnema/zut/packages/agent/tools"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 "golang.org/x/term"11 "golang.org/x/term"
12 )12 )
13 13
@@ -81,12 +81,12 @@ type Args struct {
81 // NoSkill disables ALL skill discovery for this run, including81 // NoSkill disables ALL skill discovery for this run, including
82 // the built-in skills compiled into the binary. The system82 // the built-in skills compiled into the binary. The system
83 // prompt loses its "Available skills" manifest and the `skill`83 // prompt loses its "Available skills" manifest and the `skill`
84- // tool isn't registered. Useful for running zot without any84+ // tool isn't registered. Useful for running zut without any
85 // extra context biasing the model.85 // extra context biasing the model.
86 NoSkill bool86 NoSkill bool
87 87
88 // WithSkills controls loading user-installed skills from88 // WithSkills controls loading user-installed skills from
89- // $ZOT_HOME/skills/, .zot/skills/, .claude/skills/, and89+ // $ZUT_HOME/skills/, .zut/skills/, .claude/skills/, and
90 // .agents/skills/. It defaults to true; --no-skill disables all90 // .agents/skills/. It defaults to true; --no-skill disables all
91 // skill discovery, including built-ins.91 // skill discovery, including built-ins.
92 WithSkills bool92 WithSkills bool
@@ -107,8 +107,8 @@ type Args struct {
107 // freely so automated workflows keep working.107 // freely so automated workflows keep working.
108 NoYolo bool108 NoYolo bool
109 109
110- // Yes accepts zotfile launch consent without an interactive110+ // Yes accepts zutfile launch consent without an interactive
111- // Allow? prompt (zot run -y / --yes). Durable consent receipts111+ // Allow? prompt (zut run -y / --yes). Durable consent receipts
112 // are still written for modes other than bash ask.112 // are still written for modes other than bash ask.
113 Yes bool113 Yes bool
114 114
@@ -119,7 +119,7 @@ type Args struct {
119 119
120 Prompt string // concatenated positional args120 Prompt string // concatenated positional args
121 121
122- // StartupPre is an optional zotfile entry.pre value. Interactive122+ // StartupPre is an optional zutfile entry.pre value. Interactive
123 // mode auto-submits it once at startup before InitialInput handling.123 // mode auto-submits it once at startup before InitialInput handling.
124 StartupPre string124 StartupPre string
125 125
@@ -137,8 +137,8 @@ type Args struct {
137 // locally.137 // locally.
138 Subagent string138 Subagent string
139 139
140- // AgentName/AgentDataDir/PermissionSet are populated by `zot run`140+ // AgentName/AgentDataDir/PermissionSet are populated by `zut run`
141- // for local zotfile agents. They scope sessions and enforce the141+ // for local zutfile agents. They scope sessions and enforce the
142 // manifest's declared file/bash permissions.142 // manifest's declared file/bash permissions.
143 AgentName string143 AgentName string
144 AgentDataDir string144 AgentDataDir string
@@ -364,7 +364,7 @@ func ParseArgs(in []string) (Args, error) {
364 }364 }
365 365
366 // PrintHelp writes the help text to stderr. When stderr is a TTY it366 // 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 to367+// uses the same palette as zut's TUI; when redirected it falls back to
368 // plain text with no ANSI escapes.368 // plain text with no ANSI escapes.
369 func PrintHelp(version string) {369 func PrintHelp(version string) {
370 th := tui.Dark370 th := tui.Dark
@@ -427,42 +427,42 @@ func PrintHelp(version string) {
427 fmt.Fprintln(os.Stderr)427 fmt.Fprintln(os.Stderr)
428 var headline string428 var headline string
429 if useColor {429 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."))
431 } else {431 } else {
432- headline = "zot. yet another coding agent harness."432+ headline = "zut. yet another coding agent harness."
433 }433 }
434 fmt.Fprintln(os.Stderr, headline)434 fmt.Fprintln(os.Stderr, headline)
435 fmt.Fprintln(os.Stderr, muted("ask anything, or type /help inside the tui to see commands."))435 fmt.Fprintln(os.Stderr, muted("ask anything, or type /help inside the tui to see commands."))
436 fmt.Fprintf(os.Stderr, "%s %s\n", muted("version:"), fg(version))436 fmt.Fprintf(os.Stderr, "%s %s\n", muted("version:"), fg(version))
437 437
438 section("modes",438 section("modes",
439- row{"zot", "interactive tui"},439+ row{"zut", "interactive tui"},
440- row{"zot \"prompt\"", "interactive, pre-filled prompt"},440+ row{"zut \"prompt\"", "interactive, pre-filled prompt"},
441- row{"zot -p \"prompt\"", "print final text, exit"},441+ row{"zut -p \"prompt\"", "print final text, exit"},
442- row{"zot --stream \"prompt\"", "stream assistant text live, exit"},442+ row{"zut --stream \"prompt\"", "stream assistant text live, exit"},
443- row{"zot --json \"prompt\"", "newline-delimited json events, exit"},443+ row{"zut --json \"prompt\"", "newline-delimited json events, exit"},
444- row{"zot rpc", "json-rpc loop on stdin/stdout (see docs/rpc.md)"},444+ row{"zut rpc", "json-rpc loop on stdin/stdout (see docs/rpc.md)"},
445 )445 )
446 section("extensions",446 section("extensions",
447- row{"zot ext list", "list installed extensions"},447+ row{"zut ext list", "list installed extensions"},
448- row{"zot ext install <path|git-url>", "install from a local path or Git URL"},448+ row{"zut 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"},449+ row{"zut 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"},450+ row{"zut --ext ./path/to/ext", "load an extension for this run only"},
451- row{"zot ext help", "show all extension subcommands"},451+ row{"zut ext help", "show all extension subcommands"},
452 )452 )
453 section("self-update",453 section("self-update",
454- row{"zot update", "download and install the latest release"},454+ row{"zut update", "download and install the latest release"},
455- row{"zot update --check", "show whether a new release is available"},455+ row{"zut update --check", "show whether a new release is available"},
456 )456 )
457 section("telegram",457 section("telegram",
458- row{"zot telegram-bot setup", "configure a telegram bot (from BotFather)"},458+ row{"zut telegram-bot setup", "configure a telegram bot (from BotFather)"},
459- row{"zot telegram-bot run", "foreground bridge (ctrl+c to stop)"},459+ row{"zut telegram-bot run", "foreground bridge (ctrl+c to stop)"},
460- row{"zot telegram-bot start", "background bridge (detached)"},460+ row{"zut telegram-bot start", "background bridge (detached)"},
461- row{"zot telegram-bot stop", "stop the background bridge"},461+ row{"zut telegram-bot stop", "stop the background bridge"},
462- row{"zot telegram-bot logs [-f]", "tail the background bridge log"},462+ row{"zut telegram-bot logs [-f]", "tail the background bridge log"},
463- row{"zot telegram-bot status", "config + running state"},463+ row{"zut telegram-bot status", "config + running state"},
464- row{"zot telegram-bot reset", "forget saved token"},464+ row{"zut telegram-bot reset", "forget saved token"},
465- row{"zot tg ...", "short alias for telegram-bot"},465+ row{"zut tg ...", "short alias for telegram-bot"},
466 )466 )
467 section("provider and model flags",467 section("provider and model flags",
468 row{"--provider", "provider to use (anthropic|openai|openai-codex|kimi|deepseek|google|ollama|llama.cpp)"},468 row{"--provider", "provider to use (anthropic|openai|openai-codex|kimi|deepseek|google|ollama|llama.cpp)"},
@@ -488,7 +488,7 @@ func PrintHelp(version string) {
488 row{"--no-lsp", "disable the built-in LSP/linter tool"},488 row{"--no-lsp", "disable the built-in LSP/linter tool"},
489 row{"--tools csv", "only enable the listed tools (include lsp explicitly)"},489 row{"--tools csv", "only enable the listed tools (include lsp explicitly)"},
490 row{"--no-yolo", "ask before running every tool call"},490 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"},
492 row{"--no-ext", "skip extension discovery for this run"},492 row{"--no-ext", "skip extension discovery for this run"},
493 row{"--no-skill", "skip all skill discovery for this run"},493 row{"--no-skill", "skip all skill discovery for this run"},
494 )494 )
M packages/agent/botcmd.go +63 -63
@@ -14,10 +14,10 @@ import (
14 "syscall"14 "syscall"
15 "time"15 "time"
16 16
17- "github.com/patriceckhart/zot/packages/agent/modes/bot"17+ "github.com/bnema/zut/packages/agent/modes/bot"
18- "github.com/patriceckhart/zot/packages/agent/modes/telegram"18+ "github.com/bnema/zut/packages/agent/modes/telegram"
19- "github.com/patriceckhart/zot/packages/core"19+ "github.com/bnema/zut/packages/core"
20- "github.com/patriceckhart/zot/packages/provider"20+ "github.com/bnema/zut/packages/provider"
21 "golang.org/x/term"21 "golang.org/x/term"
22 )22 )
23 23
@@ -28,7 +28,7 @@ import (
28 // inherited). See botcmd_unix.go and botcmd_windows.go.28 // inherited). See botcmd_unix.go and botcmd_windows.go.
29 var detachChild func(cmd *exec.Cmd)29 var detachChild func(cmd *exec.Cmd)
30 30
31-// runBotCommand dispatches `zot <protocol>-bot ...` subcommands via31+// runBotCommand dispatches `zut <protocol>-bot ...` subcommands via
32 // the botSpec registry. Returns true if rawArgs begins with a32 // the botSpec registry. Returns true if rawArgs begins with a
33 // recognised subcommand, false otherwise. The short alias "tg" (and33 // recognised subcommand, false otherwise. The short alias "tg" (and
34 // "mx" for matrix) are also accepted.34 // "mx" for matrix) are also accepted.
@@ -70,29 +70,29 @@ func runBotCommand(rawArgs []string, version string) (handled bool, err error) {
70 }70 }
71 }71 }
72 72
73-// botStart launches `zot <spec.subcommand> run` as a detached child73+// botStart launches `zut <spec.subcommand> run` as a detached child
74 // process, writes its pid to the spec's pid file, and returns74 // process, writes its pid to the spec's pid file, and returns
75 // immediately. Stdout/stderr of the child are redirected to the spec's75 // immediately. Stdout/stderr of the child are redirected to the spec's
76 // log file.76 // log file.
77 func botStart(spec *botSpec, rawTail []string) error {77 func botStart(spec *botSpec, rawTail []string) error {
78 // Refuse to start if another bot is already running.78 // Refuse to start if another bot is already running.
79- if pid, alive, _ := bot.IsRunningAt(spec.pidPath(ZotHome())); alive {79+ if pid, alive, _ := bot.IsRunningAt(spec.pidPath(ZutHome())); alive {
80- return fmt.Errorf("%s is already running (pid %d); use `zot %s stop` first", spec.name, pid, spec.subcommand)80+ return fmt.Errorf("%s is already running (pid %d); use `zut %s stop` first", spec.name, pid, spec.subcommand)
81 }81 }
82- _ = bot.RemovePIDFile(spec.pidPath(ZotHome())) // clear any stale pid file82+ _ = bot.RemovePIDFile(spec.pidPath(ZutHome())) // clear any stale pid file
83 83
84- if ok, cerr := spec.configured(ZotHome()); cerr != nil {84+ if ok, cerr := spec.configured(ZutHome()); cerr != nil {
85 return cerr85 return cerr
86 } else if !ok {86 } 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)
88 }88 }
89 89
90 self, err := os.Executable()90 self, err := os.Executable()
91 if err != nil {91 if err != nil {
92- return fmt.Errorf("locate zot binary: %w", err)92+ return fmt.Errorf("locate zut binary: %w", err)
93 }93 }
94 94
95- logPath := spec.logPath(ZotHome())95+ logPath := spec.logPath(ZutHome())
96 if err := os.MkdirAll(filepath.Dir(logPath), 0o755); err != nil {96 if err := os.MkdirAll(filepath.Dir(logPath), 0o755); err != nil {
97 return err97 return err
98 }98 }
@@ -107,11 +107,11 @@ func botStart(spec *botSpec, rawTail []string) error {
107 // Users hit cryptic tls / exec errors on that path; fail clearly.107 // Users hit cryptic tls / exec errors on that path; fail clearly.
108 if strings.Contains(self, string(os.PathSeparator)+"go-build") ||108 if strings.Contains(self, string(os.PathSeparator)+"go-build") ||
109 strings.Contains(self, string(os.PathSeparator)+"go-tmp") {109 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)
111 }111 }
112 112
113- // Child argv: same flags the user passed to `zot <sub> start`,113+ // Child argv: same flags the user passed to `zut <sub> start`,
114- // mapped to `zot <sub> run`. Preserves --provider, --model, --cwd, etc.114+ // mapped to `zut <sub> run`. Preserves --provider, --model, --cwd, etc.
115 args := append([]string{spec.subcommand, "run"}, rawTail...)115 args := append([]string{spec.subcommand, "run"}, rawTail...)
116 cmd := exec.Command(self, args...)116 cmd := exec.Command(self, args...)
117 cmd.Stdout = logFile117 cmd.Stdout = logFile
@@ -125,28 +125,28 @@ func botStart(spec *botSpec, rawTail []string) error {
125 if err := cmd.Start(); err != nil {125 if err := cmd.Start(); err != nil {
126 return fmt.Errorf("spawn: %w", err)126 return fmt.Errorf("spawn: %w", err)
127 }127 }
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 {
129 _ = cmd.Process.Kill()129 _ = cmd.Process.Kill()
130 return fmt.Errorf("write pid: %w", err)130 return fmt.Errorf("write pid: %w", err)
131 }131 }
132 // Don't wait() — detach. OS will reparent the child to init when we exit.132 // Don't wait() — detach. OS will reparent the child to init when we exit.
133 go func() { _ = cmd.Process.Release() }()133 go func() { _ = cmd.Process.Release() }()
134 134
135- fmt.Printf("started zot %s as pid %d (logs: %s)\n", spec.subcommand, cmd.Process.Pid, logPath)135+ fmt.Printf("started zut %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)136+ fmt.Printf("use `zut %s logs -f` to tail, `zut %s stop` to stop.\n", spec.subcommand, spec.subcommand)
137 return nil137 return nil
138 }138 }
139 139
140 // botStop sends SIGTERM to the running bot (SIGKILL if it doesn't140 // botStop sends SIGTERM to the running bot (SIGKILL if it doesn't
141 // exit within 5s) and cleans up the pid file.141 // exit within 5s) and cleans up the pid file.
142 func botStop(spec *botSpec) error {142 func botStop(spec *botSpec) error {
143- pid, alive, err := bot.IsRunningAt(spec.pidPath(ZotHome()))143+ pid, alive, err := bot.IsRunningAt(spec.pidPath(ZutHome()))
144 if err != nil {144 if err != nil {
145 return err145 return err
146 }146 }
147 if !alive {147 if !alive {
148 if pid > 0 {148 if pid > 0 {
149- _ = bot.RemovePIDFile(spec.pidPath(ZotHome()))149+ _ = bot.RemovePIDFile(spec.pidPath(ZutHome()))
150 fmt.Printf("no live process; cleared stale pid %d\n", pid)150 fmt.Printf("no live process; cleared stale pid %d\n", pid)
151 return nil151 return nil
152 }152 }
@@ -156,7 +156,7 @@ func botStop(spec *botSpec) error {
156 if err := bot.StopProcess(pid, 5*time.Second); err != nil {156 if err := bot.StopProcess(pid, 5*time.Second); err != nil {
157 return fmt.Errorf("stop pid %d: %w", pid, err)157 return fmt.Errorf("stop pid %d: %w", pid, err)
158 }158 }
159- _ = bot.RemovePIDFile(spec.pidPath(ZotHome()))159+ _ = bot.RemovePIDFile(spec.pidPath(ZutHome()))
160 fmt.Printf("stopped pid %d\n", pid)160 fmt.Printf("stopped pid %d\n", pid)
161 return nil161 return nil
162 }162 }
@@ -169,7 +169,7 @@ func botLogs(spec *botSpec, rawTail []string) error {
169 follow = true169 follow = true
170 }170 }
171 }171 }
172- p := spec.logPath(ZotHome())172+ p := spec.logPath(ZutHome())
173 f, err := os.Open(p)173 f, err := os.Open(p)
174 if errors.Is(err, os.ErrNotExist) {174 if errors.Is(err, os.ErrNotExist) {
175 fmt.Println("no log file at", p)175 fmt.Println("no log file at", p)
@@ -220,12 +220,12 @@ func botRun(spec *botSpec, rawTail []string, version string) error {
220 }220 }
221 defer closeResolvedLSP(resolved)221 defer closeResolvedLSP(resolved)
222 222
223- if ok, cerr := spec.configured(ZotHome()); cerr != nil {223+ if ok, cerr := spec.configured(ZutHome()); cerr != nil {
224 return cerr224 return cerr
225 } else if !ok {225 } 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)
227 }227 }
228- adapter, err := spec.newAdapter(ZotHome())228+ adapter, err := spec.newAdapter(ZutHome())
229 if err != nil {229 if err != nil {
230 return err230 return err
231 }231 }
@@ -256,7 +256,7 @@ func botRun(spec *botSpec, rawTail []string, version string) error {
256 256
257 var runner *bot.Runner257 var runner *bot.Runner
258 runner = bot.NewRunner(adapter, agent, bot.Config{258 runner = bot.NewRunner(adapter, agent, bot.Config{
259- ZotHome: ZotHome(),259+ ZutHome: ZutHome(),
260 Provider: resolved.Provider,260 Provider: resolved.Provider,
261 Model: resolved.Model,261 Model: resolved.Model,
262 AuthMethod: resolved.AuthMethod,262 AuthMethod: resolved.AuthMethod,
@@ -278,10 +278,10 @@ func botRun(spec *botSpec, rawTail []string, version string) error {
278 },278 },
279 })279 })
280 280
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,
282 // regardless of whether we were started directly or via `start`.282 // regardless of whether we were started directly or via `start`.
283- _ = bot.WritePIDFile(spec.pidPath(ZotHome()), os.Getpid())283+ _ = bot.WritePIDFile(spec.pidPath(ZutHome()), os.Getpid())
284- defer bot.RemovePIDFile(spec.pidPath(ZotHome()))284+ defer bot.RemovePIDFile(spec.pidPath(ZutHome()))
285 285
286 // Translate sigterm/sigint into a context cancel so the bot's goroutines286 // Translate sigterm/sigint into a context cancel so the bot's goroutines
287 // and the currently-running turn wind down cleanly.287 // and the currently-running turn wind down cleanly.
@@ -300,7 +300,7 @@ func botRun(spec *botSpec, rawTail []string, version string) error {
300 // but never prompts (no TTY picker); falls back to latest or new.300 // but never prompts (no TTY picker); falls back to latest or new.
301 func openOrCreateSessionForBot(args Args, r Resolved, ag *core.Agent, version string) (*core.Session, []any, error) {301 func openOrCreateSessionForBot(args Args, r Resolved, ag *core.Agent, version string) (*core.Session, []any, error) {
302 if args.Continue {302 if args.Continue {
303- if latest := core.LatestSession(ZotHome(), args.CWD); latest != "" {303+ if latest := core.LatestSession(ZutHome(), args.CWD); latest != "" {
304 s, msgs, err := core.OpenSession(latest)304 s, msgs, err := core.OpenSession(latest)
305 if err != nil {305 if err != nil {
306 return nil, nil, err306 return nil, nil, err
@@ -309,11 +309,11 @@ func openOrCreateSessionForBot(args Args, r Resolved, ag *core.Agent, version st
309 return s, nil, nil309 return s, nil, nil
310 }310 }
311 }311 }
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)
313 return s, nil, err313 return s, nil, err
314 }314 }
315 315
316-// maskToken returns "123456:ABC...xyz" so copies of zot telegram-bot status can be316+// maskToken returns "123456:ABC...xyz" so copies of zut telegram-bot status can be
317 // pasted into bug reports without leaking the full token.317 // pasted into bug reports without leaking the full token.
318 func maskToken(tok string) string {318 func maskToken(tok string) string {
319 if len(tok) <= 10 {319 if len(tok) <= 10 {
@@ -341,8 +341,8 @@ func telegramSpec() *botSpec {
341 aliases: []string{"tg"},341 aliases: []string{"tg"},
342 pidPath: telegram.PIDPath,342 pidPath: telegram.PIDPath,
343 logPath: telegram.LogPath,343 logPath: telegram.LogPath,
344- configured: func(zotHome string) (bool, error) {344+ configured: func(zutHome string) (bool, error) {
345- cfg, err := telegram.LoadConfig(zotHome)345+ cfg, err := telegram.LoadConfig(zutHome)
346 if err != nil {346 if err != nil {
347 return false, err347 return false, err
348 }348 }
@@ -352,37 +352,37 @@ func telegramSpec() *botSpec {
352 setup: telegramBotSetup,352 setup: telegramBotSetup,
353 status: telegramBotStatus,353 status: telegramBotStatus,
354 reset: telegramBotReset,354 reset: telegramBotReset,
355- newAdapter: func(zotHome string) (bot.BotAdapter, error) {355+ newAdapter: func(zutHome string) (bot.BotAdapter, error) {
356- cfg, err := telegram.LoadConfig(zotHome)356+ cfg, err := telegram.LoadConfig(zutHome)
357 if err != nil {357 if err != nil {
358 return nil, err358 return nil, err
359 }359 }
360 return telegram.NewAdapter(360 return telegram.NewAdapter(
361 telegram.NewClient(cfg.BotToken),361 telegram.NewClient(cfg.BotToken),
362 &cfg,362 &cfg,
363- func(c telegram.Config) error { return telegram.SaveConfig(zotHome, c) },363+ func(c telegram.Config) error { return telegram.SaveConfig(zutHome, c) },
364 ), nil364 ), nil
365 },365 },
366 }366 }
367 }367 }
368 368
369-// printTelegramBotHelp prints usage for `zot telegram-bot`.369+// printTelegramBotHelp prints usage for `zut telegram-bot`.
370 func printTelegramBotHelp() {370 func printTelegramBotHelp() {
371- fmt.Fprint(os.Stderr, `zot telegram-bot — telegram bridge371+ fmt.Fprint(os.Stderr, `zut telegram-bot — telegram bridge
372 372
373 usage:373 usage:
374- zot telegram-bot setup paste a BotFather token, verify, save374+ zut telegram-bot setup paste a BotFather token, verify, save
375- zot telegram-bot status show bridge config and whether it's running375+ zut 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)376+ zut telegram-bot run [flags] run in the foreground (ctrl+c to stop)
377- zot telegram-bot start [flags] launch in background, detach, return immediately377+ zut telegram-bot start [flags] launch in background, detach, return immediately
378- zot telegram-bot stop sigterm the running background bot, sigkill if needed378+ zut telegram-bot stop sigterm the running background bot, sigkill if needed
379- zot telegram-bot logs [--follow] tail the background bot's log file379+ zut telegram-bot logs [--follow] tail the background bot's log file
380- zot telegram-bot reset forget token + allowed user380+ zut telegram-bot reset forget token + allowed user
381 381
382 setup flow:382 setup flow:
383 1. talk to @BotFather on telegram, /newbot, copy the token383 1. talk to @BotFather on telegram, /newbot, copy the token
384- 2. run "zot telegram-bot setup" and paste the token384+ 2. run "zut telegram-bot setup" and paste the token
385- 3. run "zot telegram-bot start" (background) or "zot telegram-bot run" (foreground)385+ 3. run "zut telegram-bot start" (background) or "zut telegram-bot run" (foreground)
386 4. send /start to your bot from telegram; the first sender claims it386 4. send /start to your bot from telegram; the first sender claims it
387 387
388 while the bot is running, dm it anything and the message is forwarded388 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
391 telegram commands the bot handles directly: /help, /status, /stop.391 telegram commands the bot handles directly: /help, /status, /stop.
392 392
393 config & state:393 config & state:
394- $ZOT_HOME/bot.json # bot token + paired user (mode 0600)394+ $ZUT_HOME/bot.json # bot token + paired user (mode 0600)
395- $ZOT_HOME/bot.pid # pid of the running bot (written by run/start)395+ $ZUT_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"396+ $ZUT_HOME/logs/bot.log # stdout+stderr from "zut telegram-bot start"
397 `)397 `)
398 }398 }
399 399
@@ -422,7 +422,7 @@ func readBotToken(in *os.File, out io.Writer) (string, error) {
422 // telegramBotSetup interactively reads a bot token without echoing it on a422 // telegramBotSetup interactively reads a bot token without echoing it on a
423 // terminal, verifies it via getMe, and saves it.423 // terminal, verifies it via getMe, and saves it.
424 func telegramBotSetup(_ []string) error {424 func telegramBotSetup(_ []string) error {
425- cfg, err := telegram.LoadConfig(ZotHome())425+ cfg, err := telegram.LoadConfig(ZutHome())
426 if err != nil {426 if err != nil {
427 return err427 return err
428 }428 }
@@ -448,23 +448,23 @@ func telegramBotSetup(_ []string) error {
448 // Any stored pairing might be for a different bot; clear it.448 // Any stored pairing might be for a different bot; clear it.
449 cfg.AllowedUserID = 0449 cfg.AllowedUserID = 0
450 cfg.LastUpdateID = 0450 cfg.LastUpdateID = 0
451- if err := telegram.SaveConfig(ZotHome(), cfg); err != nil {451+ if err := telegram.SaveConfig(ZutHome(), cfg); err != nil {
452 return err452 return err
453 }453 }
454- fmt.Printf("\nsaved: @%s (id=%d) to %s\n", me.Username, me.ID, telegram.ConfigPath(ZotHome()))454+ fmt.Printf("\nsaved: @%s (id=%d) to %s\n", me.Username, me.ID, telegram.ConfigPath(ZutHome()))
455- fmt.Println("next: run `zot telegram-bot run`, then send /start to your bot from telegram.")455+ fmt.Println("next: run `zut telegram-bot run`, then send /start to your bot from telegram.")
456 return nil456 return nil
457 }457 }
458 458
459 // telegramBotStatus prints the current bot config without the token, plus459 // telegramBotStatus prints the current bot config without the token, plus
460 // whether the background process is alive.460 // whether the background process is alive.
461 func telegramBotStatus() error {461 func telegramBotStatus() error {
462- cfg, err := telegram.LoadConfig(ZotHome())462+ cfg, err := telegram.LoadConfig(ZutHome())
463 if err != nil {463 if err != nil {
464 return err464 return err
465 }465 }
466 if cfg.BotToken == "" {466 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`)")
468 return nil468 return nil
469 }469 }
470 maskedTok := maskToken(cfg.BotToken)470 maskedTok := maskToken(cfg.BotToken)
@@ -476,18 +476,18 @@ func telegramBotStatus() error {
476 fmt.Printf("paired with: telegram user id %d\n", cfg.AllowedUserID)476 fmt.Printf("paired with: telegram user id %d\n", cfg.AllowedUserID)
477 }477 }
478 fmt.Printf("last update: %d\n", cfg.LastUpdateID)478 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()))
480 480
481- pid, alive, _ := telegram.IsRunning(ZotHome())481+ pid, alive, _ := telegram.IsRunning(ZutHome())
482 switch {482 switch {
483 case alive:483 case alive:
484 fmt.Printf("process: running (pid %d)\n", pid)484 fmt.Printf("process: running (pid %d)\n", pid)
485 case pid > 0:485 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()))
487 default:487 default:
488 fmt.Println("process: stopped")488 fmt.Println("process: stopped")
489 }489 }
490- logPath := telegram.LogPath(ZotHome())490+ logPath := telegram.LogPath(ZutHome())
491 if fi, err := os.Stat(logPath); err == nil {491 if fi, err := os.Stat(logPath); err == nil {
492 fmt.Printf("log file: %s (%d bytes)\n", logPath, fi.Size())492 fmt.Printf("log file: %s (%d bytes)\n", logPath, fi.Size())
493 }493 }
@@ -496,7 +496,7 @@ func telegramBotStatus() error {
496 496
497 // telegramBotReset wipes the on-disk bot.json entry.497 // telegramBotReset wipes the on-disk bot.json entry.
498 func telegramBotReset() error {498 func telegramBotReset() error {
499- p := telegram.ConfigPath(ZotHome())499+ p := telegram.ConfigPath(ZutHome())
500 if _, err := os.Stat(p); os.IsNotExist(err) {500 if _, err := os.Stat(p); os.IsNotExist(err) {
501 fmt.Println("no bot config to reset")501 fmt.Println("no bot config to reset")
502 return nil502 return nil
M packages/agent/botspec.go +5 -5
@@ -1,7 +1,7 @@
1 package agent1 package agent
2 2
3 import (3 import (
4- "github.com/patriceckhart/zot/packages/agent/modes/bot"4+ "github.com/bnema/zut/packages/agent/modes/bot"
5 )5 )
6 6
7 // botSpec describes one bot protocol to the generic daemon CLI in7 // botSpec describes one bot protocol to the generic daemon CLI in
@@ -13,12 +13,12 @@ type botSpec struct {
13 subcommand string // "telegram-bot", "matrix-bot"13 subcommand string // "telegram-bot", "matrix-bot"
14 aliases []string // {"tg"}, {"mx"}14 aliases []string // {"tg"}, {"mx"}
15 15
16- pidPath func(zotHome string) string16+ pidPath func(zutHome string) string
17- logPath func(zotHome string) string17+ logPath func(zutHome string) string
18 18
19 // configured reports whether setup has been completed (e.g. a19 // configured reports whether setup has been completed (e.g. a
20 // token is present), with a hint error message when it hasn't.20 // 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)
22 22
23 printHelp func()23 printHelp func()
24 setup func(tail []string) error24 setup func(tail []string) error
@@ -27,7 +27,7 @@ type botSpec struct {
27 27
28 // newAdapter builds the protocol adapter for the standalone28 // newAdapter builds the protocol adapter for the standalone
29 // daemon. Credential refresh stays generic in botRun.29 // daemon. Credential refresh stays generic in botRun.
30- newAdapter func(zotHome string) (bot.BotAdapter, error)30+ newAdapter func(zutHome string) (bot.BotAdapter, error)
31 }31 }
32 32
33 // botSpecs is the registry the dispatcher walks. Matrix is appended33 // botSpecs is the registry the dispatcher walks. Matrix is appended
M packages/agent/build.go +31 -31
@@ -8,13 +8,13 @@ import (
8 "runtime"8 "runtime"
9 "strings"9 "strings"
10 10
11- zotdocs "github.com/patriceckhart/zot"11+ zutdocs "github.com/bnema/zut"
12- "github.com/patriceckhart/zot/packages/agent/lsp"12+ "github.com/bnema/zut/packages/agent/lsp"
13- "github.com/patriceckhart/zot/packages/agent/skills"13+ "github.com/bnema/zut/packages/agent/skills"
14- "github.com/patriceckhart/zot/packages/agent/subagents"14+ "github.com/bnema/zut/packages/agent/subagents"
15- "github.com/patriceckhart/zot/packages/agent/tools"15+ "github.com/bnema/zut/packages/agent/tools"
16- "github.com/patriceckhart/zot/packages/core"16+ "github.com/bnema/zut/packages/core"
17- "github.com/patriceckhart/zot/packages/provider"17+ "github.com/bnema/zut/packages/provider"
18 )18 )
19 19
20 // ContextFile is an instruction file loaded into the system prompt.20 // ContextFile is an instruction file loaded into the system prompt.
@@ -136,7 +136,7 @@ func (r *Resolved) MergeExtensionTools(mgr ExtensionToolSource) {
136 Tools: r.ToolSummary,136 Tools: r.ToolSummary,
137 Custom: r.systemCustom,137 Custom: r.systemCustom,
138 Append: r.systemAppend,138 Append: r.systemAppend,
139- ZotDocsDir: filepath.Join(ZotHome(), "docs"),139+ ZutDocsDir: filepath.Join(ZutHome(), "docs"),
140 })140 })
141 }141 }
142 142
@@ -217,7 +217,7 @@ func toolSummariesFromRegistry(reg core.Registry, cached map[string]string) []To
217 return out217 return out
218 }218 }
219 219
220-// defaultModelForProvider returns the model id zot prefers when the220+// defaultModelForProvider returns the model id zut prefers when the
221 // caller didn't pick one. Mirrors the per-provider switch used at221 // caller didn't pick one. Mirrors the per-provider switch used at
222 // multiple points in Resolve; centralised so the unknown-model222 // multiple points in Resolve; centralised so the unknown-model
223 // recovery path and the no-model-configured path can't drift.223 // recovery path and the no-model-configured path can't drift.
@@ -289,7 +289,7 @@ func defaultModelForProvider(prov string) string {
289 }289 }
290 }290 }
291 291
292-// knownProviders is the set of provider ids zot recognises. Used by292+// knownProviders is the set of provider ids zut recognises. Used by
293 // Resolve to validate args.Provider, by extension-callers, and by the293 // Resolve to validate args.Provider, by extension-callers, and by the
294 // auto-fallback logic that picks any logged-in provider when the user's294 // auto-fallback logic that picks any logged-in provider when the user's
295 // preferred one has no credentials.295 // preferred one has no credentials.
@@ -495,7 +495,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
495 // If the user did NOT explicitly pick a provider (neither via --provider495 // If the user did NOT explicitly pick a provider (neither via --provider
496 // nor by saving one in config.json) and the default one has no496 // nor by saving one in config.json) and the default one has no
497 // credentials, auto-fall-back to whichever provider is actually logged497 // credentials, auto-fall-back to whichever provider is actually logged
498- // in. That way running plain `zot` after `/login` (any provider) never498+ // in. That way running plain `zut` after `/login` (any provider) never
499 // shows a "not logged in" banner.499 // shows a "not logged in" banner.
500 //500 //
501 // Critical: when the user HAS saved a provider in config.json (e.g.501 // 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) {
518 // env-based credential is discovered, e.g. an env-only518 // env-based credential is discovered, e.g. an env-only
519 // amazon-bedrock setup (AWS_BEARER_TOKEN_BEDROCK / AWS_PROFILE /519 // amazon-bedrock setup (AWS_BEARER_TOKEN_BEDROCK / AWS_PROFILE /
520 // IAM keys) when no config.json pins the provider, such as after520 // IAM keys) when no config.json pins the provider, such as after
521- // pointing ZOT_HOME at a fresh home dir. Iteration order of521+ // pointing ZUT_HOME at a fresh home dir. Iteration order of
522 // knownProviders defines fallback priority. Local providers without a522 // knownProviders defines fallback priority. Local providers without a
523 // default model are skipped because selecting either one here would523 // default model are skipped because selecting either one here would
524 // fail before the user can choose a model.524 // fail before the user can choose a model.
@@ -591,7 +591,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
591 if err != nil {591 if err != nil {
592 // The model the user (or persisted config) asked for is no592 // The model the user (or persisted config) asked for is no
593 // longer in the active catalogue — they probably removed it593 // 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.
595 // Refusing to launch is the wrong move: it strands the user595 // Refusing to launch is the wrong move: it strands the user
596 // with no way to even open the TUI and pick a new model.596 // with no way to even open the TUI and pick a new model.
597 // Fall back to the provider's default, warn on stderr, and,597 // Fall back to the provider's default, warn on stderr, and,
@@ -600,7 +600,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
600 // doesn't repeat on every launch.600 // doesn't repeat on every launch.
601 601
602 // Gateway providers can accept route-qualified ids that are not in602 // Gateway providers can accept route-qualified ids that are not in
603- // zot's local catalog yet, for example OpenRouter's603+ // zut's local catalog yet, for example OpenRouter's
604 // "deepseek/deepseek-v4-flash". Preserve only route-qualified ids;604 // "deepseek/deepseek-v4-flash". Preserve only route-qualified ids;
605 // plain unknown values are likely typos and should still fall back.605 // plain unknown values are likely typos and should still fall back.
606 if isGatewayProvider(provName) && isGatewayRoutedModelID(model) {606 if isGatewayProvider(provName) && isGatewayRoutedModelID(model) {
@@ -628,7 +628,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
628 }628 }
629 }629 }
630 fmt.Fprintf(os.Stderr,630 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",
632 model, fm.ID)632 model, fm.ID)
633 if args.Model == "" && cfg.Model == model {633 if args.Model == "" && cfg.Model == model {
634 cfg.Model = fm.ID634 cfg.Model = fm.ID
@@ -677,7 +677,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
677 }677 }
678 678
679 if credErr != nil && requireCred {679 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",
681 credErr, envVarName(provName))681 credErr, envVarName(provName))
682 }682 }
683 683
@@ -692,7 +692,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
692 lspEnabled := !args.NoLSP && cfg.LSPEnabledFor(subagentSession)692 lspEnabled := !args.NoLSP && cfg.LSPEnabledFor(subagentSession)
693 reg := buildToolRegistry(args, args.CWD, sandbox, lspEnabled, cfg.LSPDiagnosticsOnWriteEnabled(subagentSession), cfg.LSPDiagnosticsOnEditEnabled(subagentSession))693 reg := buildToolRegistry(args, args.CWD, sandbox, lspEnabled, cfg.LSPDiagnosticsOnWriteEnabled(subagentSession), cfg.LSPDiagnosticsOnEditEnabled(subagentSession))
694 694
695- docsDir, _ := zotdocs.EnsureInstalled(ZotHome())695+ docsDir, _ := zutdocs.EnsureInstalled(ZutHome())
696 696
697 // Skill discovery: scan project + global locations + built-in697 // Skill discovery: scan project + global locations + built-in
698 // skills shipped with the binary. If any are found, register698 // skills shipped with the binary. If any are found, register
@@ -711,7 +711,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
711 if !args.NoSkill && (selectedProfile == nil || selectedProfile.InheritSkills == nil || *selectedProfile.InheritSkills) {711 if !args.NoSkill && (selectedProfile == nil || selectedProfile.InheritSkills == nil || *selectedProfile.InheritSkills) {
712 skillsEnabled = true712 skillsEnabled = true
713 homeDir, _ := os.UserHomeDir()713 homeDir, _ := os.UserHomeDir()
714- discovered, _ = skills.Discover(ZotHome(), args.CWD, homeDir, args.WithSkills)714+ discovered, _ = skills.Discover(ZutHome(), args.CWD, homeDir, args.WithSkills)
715 if len(discovered) > 0 {715 if len(discovered) > 0 {
716 skillTool = skills.NewTool(discovered)716 skillTool = skills.NewTool(discovered)
717 reg[skillTool.Name()] = skillTool717 reg[skillTool.Name()] = skillTool
@@ -724,7 +724,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
724 724
725 contextFiles := []ContextFile(nil)725 contextFiles := []ContextFile(nil)
726 if selectedProfile == nil || selectedProfile.InheritProjectContext == nil || *selectedProfile.InheritProjectContext {726 if selectedProfile == nil || selectedProfile.InheritProjectContext == nil || *selectedProfile.InheritProjectContext {
727- contextFiles = loadAgentsContext(args.CWD, ZotHome())727+ contextFiles = loadAgentsContext(args.CWD, ZutHome())
728 }728 }
729 append_ := []string(nil)729 append_ := []string(nil)
730 if cfg.PonytailModeEnabled() {730 if cfg.PonytailModeEnabled() {
@@ -754,13 +754,13 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
754 754
755 // Custom system prompt resolution order:755 // Custom system prompt resolution order:
756 // 1. --system-prompt flag (highest priority; ad-hoc per run)756 // 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)
758 // 3. built-in default (defaultIdentity + defaultGuidelines)758 // 3. built-in default (defaultIdentity + defaultGuidelines)
759 custom := args.SystemPrompt759 custom := args.SystemPrompt
760 if selectedProfile != nil && selectedProfile.SystemPromptMode == "replace" {760 if selectedProfile != nil && selectedProfile.SystemPromptMode == "replace" {
761 custom = selectedProfile.SystemPrompt761 custom = selectedProfile.SystemPrompt
762 } else if custom == "" {762 } else if custom == "" {
763- custom = readUserSystemPrompt(ZotHome())763+ custom = readUserSystemPrompt(ZutHome())
764 }764 }
765 765
766 sys := BuildSystemPrompt(SystemPromptOpts{766 sys := BuildSystemPrompt(SystemPromptOpts{
@@ -768,7 +768,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
768 Tools: summaries,768 Tools: summaries,
769 Custom: custom,769 Custom: custom,
770 Append: append_,770 Append: append_,
771- ZotDocsDir: docsDir,771+ ZutDocsDir: docsDir,
772 })772 })
773 773
774 reasoning := provider.NormalizeReasoning(firstNonEmpty(args.Reasoning, cfg.Reasoning))774 reasoning := provider.NormalizeReasoning(firstNonEmpty(args.Reasoning, cfg.Reasoning))
@@ -818,16 +818,16 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
818 }, nil818 }, nil
819 }819 }
820 820
821-// readUserSystemPrompt looks for $ZOT_HOME/SYSTEM.md and returns its821+// readUserSystemPrompt looks for $ZUT_HOME/SYSTEM.md and returns its
822 // trimmed contents, or "" when the file is missing / unreadable /822 // trimmed contents, or "" when the file is missing / unreadable /
823 // empty. Errors are intentionally swallowed: the file is optional,823 // empty. Errors are intentionally swallowed: the file is optional,
824 // and any failure to read it should fall back to the built-in824 // and any failure to read it should fall back to the built-in
825 // default system prompt rather than crash the run.825 // default system prompt rather than crash the run.
826-func readUserSystemPrompt(zotHome string) string {826+func readUserSystemPrompt(zutHome string) string {
827- if zotHome == "" {827+ if zutHome == "" {
828 return ""828 return ""
829 }829 }
830- path := filepath.Join(zotHome, "SYSTEM.md")830+ path := filepath.Join(zutHome, "SYSTEM.md")
831 raw, err := os.ReadFile(path)831 raw, err := os.ReadFile(path)
832 if err != nil {832 if err != nil {
833 return ""833 return ""
@@ -836,10 +836,10 @@ func readUserSystemPrompt(zotHome string) string {
836 }836 }
837 837
838 // loadAgentsContext loads optional AGENTS.md instruction files. No838 // loadAgentsContext loads optional AGENTS.md instruction files. No
839-// default file is created or required: zot only includes files that839+// default file is created or required: zut only includes files that
840-// already exist. Global instructions ($ZOT_HOME/AGENTS.md) come first,840+// already exist. Global instructions ($ZUT_HOME/AGENTS.md) come first,
841 // followed by project instructions from the filesystem root down to cwd.841 // 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 {
843 var files []ContextFile843 var files []ContextFile
844 seen := map[string]bool{}844 seen := map[string]bool{}
845 add := func(path string) {845 add := func(path string) {
@@ -877,7 +877,7 @@ func loadAgentsContext(cwd, zotHome string) []ContextFile {
877 }877 }
878 }878 }
879 879
880- addFirstFromDir(zotHome)880+ addFirstFromDir(zutHome)
881 881
882 if cwd != "" {882 if cwd != "" {
883 abs, err := filepath.Abs(cwd)883 abs, err := filepath.Abs(cwd)
@@ -1250,7 +1250,7 @@ func kimiCodeHeaders() map[string]string {
1250 }1250 }
1251 }1251 }
1252 if deviceID == "" {1252 if deviceID == "" {
1253- deviceID = "zot"1253+ deviceID = "zut"
1254 }1254 }
1255 return map[string]string{1255 return map[string]string{
1256 "User-Agent": "KimiCLI/1.41.0",1256 "User-Agent": "KimiCLI/1.41.0",
M packages/agent/build_test.go +37 -37
@@ -10,10 +10,10 @@ import (
10 "strings"10 "strings"
11 "testing"11 "testing"
12 12
13- "github.com/patriceckhart/zot/packages/agent/skills"13+ "github.com/bnema/zut/packages/agent/skills"
14- "github.com/patriceckhart/zot/packages/agent/tools"14+ "github.com/bnema/zut/packages/agent/tools"
15- "github.com/patriceckhart/zot/packages/core"15+ "github.com/bnema/zut/packages/core"
16- "github.com/patriceckhart/zot/packages/provider"16+ "github.com/bnema/zut/packages/provider"
17 )17 )
18 18
19 type bundledSkillSource struct {19 type bundledSkillSource struct {
@@ -140,16 +140,16 @@ func TestAutoSubagentsToolPoliciesTrackEachTool(t *testing.T) {
140 140
141 func TestReadAgentsContextLoadsGlobalAndAncestors(t *testing.T) {141 func TestReadAgentsContextLoadsGlobalAndAncestors(t *testing.T) {
142 root := t.TempDir()142 root := t.TempDir()
143- zotHome := filepath.Join(root, "zot-home")143+ zutHome := filepath.Join(root, "zut-home")
144 project := filepath.Join(root, "repo")144 project := filepath.Join(root, "repo")
145 nested := filepath.Join(project, "packages", "app")145 nested := filepath.Join(project, "packages", "app")
146- if err := os.MkdirAll(zotHome, 0o755); err != nil {146+ if err := os.MkdirAll(zutHome, 0o755); err != nil {
147 t.Fatal(err)147 t.Fatal(err)
148 }148 }
149 if err := os.MkdirAll(nested, 0o755); err != nil {149 if err := os.MkdirAll(nested, 0o755); err != nil {
150 t.Fatal(err)150 t.Fatal(err)
151 }151 }
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 {
153 t.Fatal(err)153 t.Fatal(err)
154 }154 }
155 if err := os.WriteFile(filepath.Join(project, "AGENTS.md"), []byte("repo rule"), 0o644); err != nil {155 if err := os.WriteFile(filepath.Join(project, "AGENTS.md"), []byte("repo rule"), 0o644); err != nil {
@@ -159,12 +159,12 @@ func TestReadAgentsContextLoadsGlobalAndAncestors(t *testing.T) {
159 t.Fatal(err)159 t.Fatal(err)
160 }160 }
161 161
162- files := loadAgentsContext(nested, zotHome)162+ files := loadAgentsContext(nested, zutHome)
163 if len(files) != 3 {163 if len(files) != 3 {
164 t.Fatalf("loaded %d context files, want 3: %#v", len(files), files)164 t.Fatalf("loaded %d context files, want 3: %#v", len(files), files)
165 }165 }
166 wantPaths := []string{166 wantPaths := []string{
167- filepath.Join(zotHome, "AGENTS.md"),167+ filepath.Join(zutHome, "AGENTS.md"),
168 filepath.Join(project, "AGENTS.md"),168 filepath.Join(project, "AGENTS.md"),
169 filepath.Join(nested, "AGENTS.md"),169 filepath.Join(nested, "AGENTS.md"),
170 }170 }
@@ -190,7 +190,7 @@ func TestFindSubagentProfileReportsDiscoveryFailure(t *testing.T) {
190 if err := os.WriteFile(profileSource, []byte("not a directory"), 0o600); err != nil {190 if err := os.WriteFile(profileSource, []byte("not a directory"), 0o600); err != nil {
191 t.Fatal(err)191 t.Fatal(err)
192 }192 }
193- t.Setenv("ZOT_AGENT_PROFILES", profileSource)193+ t.Setenv("ZUT_AGENT_PROFILES", profileSource)
194 194
195 profile, err := findSubagentProfile("", "reviewer")195 profile, err := findSubagentProfile("", "reviewer")
196 if profile != nil {196 if profile != nil {
@@ -207,7 +207,7 @@ func TestFindSubagentProfileReportsDiscoveryFailure(t *testing.T) {
207 func TestResolveIncludesNamedSubagentsListWhenAutoSubagentsIsEnabled(t *testing.T) {207 func TestResolveIncludesNamedSubagentsListWhenAutoSubagentsIsEnabled(t *testing.T) {
208 root := t.TempDir()208 root := t.TempDir()
209 home := filepath.Join(root, "home")209 home := filepath.Join(root, "home")
210- zotHome := filepath.Join(root, "zot-home")210+ zutHome := filepath.Join(root, "zut-home")
211 project := filepath.Join(root, "repo")211 project := filepath.Join(root, "repo")
212 if err := os.MkdirAll(filepath.Join(home, ".agents", "agents"), 0o755); err != nil {212 if err := os.MkdirAll(filepath.Join(home, ".agents", "agents"), 0o755); err != nil {
213 t.Fatal(err)213 t.Fatal(err)
@@ -216,8 +216,8 @@ func TestResolveIncludesNamedSubagentsListWhenAutoSubagentsIsEnabled(t *testing.
216 t.Fatal(err)216 t.Fatal(err)
217 }217 }
218 t.Setenv("HOME", home)218 t.Setenv("HOME", home)
219- t.Setenv("ZOT_HOME", zotHome)219+ t.Setenv("ZUT_HOME", zutHome)
220- t.Setenv("ZOT_AGENT_PROFILES", filepath.Join(home, ".agents", "agents"))220+ t.Setenv("ZUT_AGENT_PROFILES", filepath.Join(home, ".agents", "agents"))
221 enabled := true221 enabled := true
222 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", AutoSubagentsEnabled: &enabled}); err != nil {222 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", AutoSubagentsEnabled: &enabled}); err != nil {
223 t.Fatal(err)223 t.Fatal(err)
@@ -247,7 +247,7 @@ Review without editing.
247 func TestResolveAppliesSelectedSubagentProfile(t *testing.T) {247 func TestResolveAppliesSelectedSubagentProfile(t *testing.T) {
248 root := t.TempDir()248 root := t.TempDir()
249 home := filepath.Join(root, "home")249 home := filepath.Join(root, "home")
250- zotHome := filepath.Join(root, "zot-home")250+ zutHome := filepath.Join(root, "zut-home")
251 project := filepath.Join(root, "repo")251 project := filepath.Join(root, "repo")
252 if err := os.MkdirAll(filepath.Join(home, ".agents", "agents"), 0o755); err != nil {252 if err := os.MkdirAll(filepath.Join(home, ".agents", "agents"), 0o755); err != nil {
253 t.Fatal(err)253 t.Fatal(err)
@@ -256,12 +256,12 @@ func TestResolveAppliesSelectedSubagentProfile(t *testing.T) {
256 t.Fatal(err)256 t.Fatal(err)
257 }257 }
258 t.Setenv("HOME", home)258 t.Setenv("HOME", home)
259- t.Setenv("ZOT_HOME", zotHome)259+ t.Setenv("ZUT_HOME", zutHome)
260- t.Setenv("ZOT_AGENT_PROFILES", filepath.Join(home, ".agents", "agents"))260+ t.Setenv("ZUT_AGENT_PROFILES", filepath.Join(home, ".agents", "agents"))
261 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", Reasoning: "high"}); err != nil {261 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", Reasoning: "high"}); err != nil {
262 t.Fatal(err)262 t.Fatal(err)
263 }263 }
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 {
265 t.Fatal(err)265 t.Fatal(err)
266 }266 }
267 profile := `---267 profile := `---
@@ -320,7 +320,7 @@ func TestResolveSubagentFastModeUsesHostSettingAsAnUpperBound(t *testing.T) {
320 t.Run(tc.name, func(t *testing.T) {320 t.Run(tc.name, func(t *testing.T) {
321 root := t.TempDir()321 root := t.TempDir()
322 home := filepath.Join(root, "home")322 home := filepath.Join(root, "home")
323- zotHome := filepath.Join(root, "zot-home")323+ zutHome := filepath.Join(root, "zut-home")
324 project := filepath.Join(root, "repo")324 project := filepath.Join(root, "repo")
325 profilesDir := filepath.Join(home, ".agents", "agents")325 profilesDir := filepath.Join(home, ".agents", "agents")
326 if err := os.MkdirAll(profilesDir, 0o755); err != nil {326 if err := os.MkdirAll(profilesDir, 0o755); err != nil {
@@ -330,8 +330,8 @@ func TestResolveSubagentFastModeUsesHostSettingAsAnUpperBound(t *testing.T) {
330 t.Fatal(err)330 t.Fatal(err)
331 }331 }
332 t.Setenv("HOME", home)332 t.Setenv("HOME", home)
333- t.Setenv("ZOT_HOME", zotHome)333+ t.Setenv("ZUT_HOME", zutHome)
334- t.Setenv("ZOT_AGENT_PROFILES", profilesDir)334+ t.Setenv("ZUT_AGENT_PROFILES", profilesDir)
335 335
336 cfg := Config{Provider: "openai", Model: "gpt-5"}336 cfg := Config{Provider: "openai", Model: "gpt-5"}
337 cfg.FastMode = &tc.hostFastMode337 cfg.FastMode = &tc.hostFastMode
@@ -376,12 +376,12 @@ func TestReadAgentsContextMissingFilesIsEmpty(t *testing.T) {
376 // TestResolveFallsBackWhenConfiguredModelIsGone reproduces the376 // TestResolveFallsBackWhenConfiguredModelIsGone reproduces the
377 // startup failure caught by the user's screenshot: the persisted377 // startup failure caught by the user's screenshot: the persisted
378 // config.json points at a model id that's no longer in the active378 // config.json points at a model id that's no longer in the active
379-// catalogue (because they edited models.json or zot's bundled379+// catalogue (because they edited models.json or zut's bundled
380 // catalogue changed). Resolve must NOT error — strands the user380 // catalogue changed). Resolve must NOT error — strands the user
381 // with no way to fix it from the TUI — and should repair the config381 // with no way to fix it from the TUI — and should repair the config
382 // so the next launch is silent.382 // so the next launch is silent.
383 func TestResolveFallsBackWhenConfiguredModelIsGone(t *testing.T) {383 func TestResolveFallsBackWhenConfiguredModelIsGone(t *testing.T) {
384- t.Setenv("ZOT_HOME", t.TempDir())384+ t.Setenv("ZUT_HOME", t.TempDir())
385 t.Setenv("OPENAI_API_KEY", "test-key")385 t.Setenv("OPENAI_API_KEY", "test-key")
386 // Persist a stale model id.386 // Persist a stale model id.
387 stale := "gpt-5.5-pro-not-real"387 stale := "gpt-5.5-pro-not-real"
@@ -421,7 +421,7 @@ func TestResolveAppliesJailByDefault(t *testing.T) {
421 {name: "enabled starts locked", config: Config{Provider: "openai", Model: "gpt-5", JailByDefault: boolPtr(true)}, want: true},421 {name: "enabled starts locked", config: Config{Provider: "openai", Model: "gpt-5", JailByDefault: boolPtr(true)}, want: true},
422 } {422 } {
423 t.Run(tc.name, func(t *testing.T) {423 t.Run(tc.name, func(t *testing.T) {
424- t.Setenv("ZOT_HOME", t.TempDir())424+ t.Setenv("ZUT_HOME", t.TempDir())
425 t.Setenv("OPENAI_API_KEY", "test-key")425 t.Setenv("OPENAI_API_KEY", "test-key")
426 if err := SaveConfig(tc.config); err != nil {426 if err := SaveConfig(tc.config); err != nil {
427 t.Fatal(err)427 t.Fatal(err)
@@ -445,7 +445,7 @@ func boolPtr(v bool) *bool { return &v }
445 // persisted config. If the user passed --model X explicitly and X is445 // persisted config. If the user passed --model X explicitly and X is
446 // unknown, we still fall back, but we don't touch their config.446 // unknown, we still fall back, but we don't touch their config.
447 func TestResolveExplicitFlagStaleDoesNotRepairConfig(t *testing.T) {447 func TestResolveExplicitFlagStaleDoesNotRepairConfig(t *testing.T) {
448- t.Setenv("ZOT_HOME", t.TempDir())448+ t.Setenv("ZUT_HOME", t.TempDir())
449 t.Setenv("OPENAI_API_KEY", "test-key")449 t.Setenv("OPENAI_API_KEY", "test-key")
450 good := "gpt-5"450 good := "gpt-5"
451 if err := SaveConfig(Config{Provider: "openai", Model: good}); err != nil {451 if err := SaveConfig(Config{Provider: "openai", Model: good}); err != nil {
@@ -466,7 +466,7 @@ func TestResolveExplicitFlagStaleDoesNotRepairConfig(t *testing.T) {
466 }466 }
467 467
468 func TestResolveOpenRouterPreservesSavedRoutedModelID(t *testing.T) {468 func TestResolveOpenRouterPreservesSavedRoutedModelID(t *testing.T) {
469- t.Setenv("ZOT_HOME", t.TempDir())469+ t.Setenv("ZUT_HOME", t.TempDir())
470 t.Setenv("OPENROUTER_API_KEY", "test-key")470 t.Setenv("OPENROUTER_API_KEY", "test-key")
471 want := "deepseek/deepseek-v4-flash"471 want := "deepseek/deepseek-v4-flash"
472 if err := SaveConfig(Config{Provider: "openrouter", Model: want}); err != nil {472 if err := SaveConfig(Config{Provider: "openrouter", Model: want}); err != nil {
@@ -489,7 +489,7 @@ func TestResolveOpenRouterPreservesSavedRoutedModelID(t *testing.T) {
489 }489 }
490 490
491 func TestResolveGatewayPlainUnknownModelFallsBack(t *testing.T) {491 func TestResolveGatewayPlainUnknownModelFallsBack(t *testing.T) {
492- t.Setenv("ZOT_HOME", t.TempDir())492+ t.Setenv("ZUT_HOME", t.TempDir())
493 t.Setenv("OPENROUTER_API_KEY", "test-key")493 t.Setenv("OPENROUTER_API_KEY", "test-key")
494 stale := "not-a-routed-model"494 stale := "not-a-routed-model"
495 if err := SaveConfig(Config{Provider: "openrouter", Model: stale}); err != nil {495 if err := SaveConfig(Config{Provider: "openrouter", Model: stale}); err != nil {
@@ -509,12 +509,12 @@ func TestResolveGatewayPlainUnknownModelFallsBack(t *testing.T) {
509 }509 }
510 510
511 // TestResolveEnvOnlyBedrockDiscoveredWithoutConfig reproduces issue511 // TestResolveEnvOnlyBedrockDiscoveredWithoutConfig reproduces issue
512-// #15: pointing ZOT_HOME at a fresh dir drops the persisted512+// #15: pointing ZUT_HOME at a fresh dir drops the persisted
513 // config.json (which pinned provider=amazon-bedrock). Resolve must513 // config.json (which pinned provider=amazon-bedrock). Resolve must
514 // still discover bedrock from the AWS env vars instead of falling back514 // still discover bedrock from the AWS env vars instead of falling back
515 // to anthropic and reporting "not logged in".515 // to anthropic and reporting "not logged in".
516 func TestResolveEnvOnlyBedrockDiscoveredWithoutConfig(t *testing.T) {516 func TestResolveEnvOnlyBedrockDiscoveredWithoutConfig(t *testing.T) {
517- t.Setenv("ZOT_HOME", t.TempDir()) // fresh home: no config.json517+ t.Setenv("ZUT_HOME", t.TempDir()) // fresh home: no config.json
518 // Disable the Kimi CLI token fallback so a developer machine with a518 // Disable the Kimi CLI token fallback so a developer machine with a
519 // real Kimi CLI login doesn't pre-empt bedrock in the scan.519 // real Kimi CLI login doesn't pre-empt bedrock in the scan.
520 if err := SetKimiCLIFallbackDisabled(true); err != nil {520 if err := SetKimiCLIFallbackDisabled(true); err != nil {
@@ -540,7 +540,7 @@ func TestResolveEnvOnlyBedrockDiscoveredWithoutConfig(t *testing.T) {
540 }540 }
541 541
542 func TestResolveOllamaUsesModelBaseURLBeforeDefault(t *testing.T) {542 func TestResolveOllamaUsesModelBaseURLBeforeDefault(t *testing.T) {
543- t.Setenv("ZOT_HOME", t.TempDir())543+ t.Setenv("ZUT_HOME", t.TempDir())
544 provider.SetLiveModels(nil)544 provider.SetLiveModels(nil)
545 defer provider.SetLiveModels(nil)545 defer provider.SetLiveModels(nil)
546 provider.SetUserModels([]provider.Model{{546 provider.SetUserModels([]provider.Model{{
@@ -562,7 +562,7 @@ func TestResolveOllamaUsesModelBaseURLBeforeDefault(t *testing.T) {
562 }562 }
563 563
564 func TestResolveUsesInheritedSupervisorCredential(t *testing.T) {564 func TestResolveUsesInheritedSupervisorCredential(t *testing.T) {
565- t.Setenv("ZOT_HOME", t.TempDir())565+ t.Setenv("ZUT_HOME", t.TempDir())
566 t.Setenv("OPENAI_API_KEY", "")566 t.Setenv("OPENAI_API_KEY", "")
567 567
568 r, err := Resolve(Args{568 r, err := Resolve(Args{
@@ -581,7 +581,7 @@ func TestResolveUsesInheritedSupervisorCredential(t *testing.T) {
581 }581 }
582 582
583 func TestResolveLlamaCPPUsesRouterInferenceURL(t *testing.T) {583 func TestResolveLlamaCPPUsesRouterInferenceURL(t *testing.T) {
584- t.Setenv("ZOT_HOME", t.TempDir())584+ t.Setenv("ZUT_HOME", t.TempDir())
585 t.Setenv("LLAMA_BASE_URL", "http://127.0.0.1:8080/v1/")585 t.Setenv("LLAMA_BASE_URL", "http://127.0.0.1:8080/v1/")
586 t.Setenv("LLAMA_API_KEY", "")586 t.Setenv("LLAMA_API_KEY", "")
587 provider.SetManagedModels(nil)587 provider.SetManagedModels(nil)
@@ -603,7 +603,7 @@ func TestResolveLlamaCPPUsesRouterInferenceURL(t *testing.T) {
603 }603 }
604 604
605 func TestResolveLlamaCPPUsesStoredLogin(t *testing.T) {605 func TestResolveLlamaCPPUsesStoredLogin(t *testing.T) {
606- t.Setenv("ZOT_HOME", t.TempDir())606+ t.Setenv("ZUT_HOME", t.TempDir())
607 t.Setenv("LLAMA_BASE_URL", "")607 t.Setenv("LLAMA_BASE_URL", "")
608 t.Setenv("LLAMA_API_KEY", "")608 t.Setenv("LLAMA_API_KEY", "")
609 if err := AuthStoreFor().SetEndpointCredential("llama.cpp", "http://localhost:9090", "stored-key"); err != nil {609 if err := AuthStoreFor().SetEndpointCredential("llama.cpp", "http://localhost:9090", "stored-key"); err != nil {
@@ -620,7 +620,7 @@ func TestResolveLlamaCPPUsesStoredLogin(t *testing.T) {
620 }620 }
621 621
622 func TestResolveCustomProviderModelBaseURLBeatsProviderBaseURL(t *testing.T) {622 func TestResolveCustomProviderModelBaseURLBeatsProviderBaseURL(t *testing.T) {
623- t.Setenv("ZOT_HOME", t.TempDir())623+ t.Setenv("ZUT_HOME", t.TempDir())
624 t.Setenv("MY_COMPANY_API_KEY", "test-key")624 t.Setenv("MY_COMPANY_API_KEY", "test-key")
625 path := filepath.Join(t.TempDir(), "models.json")625 path := filepath.Join(t.TempDir(), "models.json")
626 if err := os.WriteFile(path, []byte(`{626 if err := os.WriteFile(path, []byte(`{
@@ -702,7 +702,7 @@ func TestCustomProviderUsesOpenAIResponsesAPI(t *testing.T) {
702 }702 }
703 703
704 func TestResolveCustomProviderInsecureFromModelsJSONBaseURL(t *testing.T) {704 func TestResolveCustomProviderInsecureFromModelsJSONBaseURL(t *testing.T) {
705- t.Setenv("ZOT_HOME", t.TempDir())705+ t.Setenv("ZUT_HOME", t.TempDir())
706 t.Setenv("LOCAL_PROXY_API_KEY", "test-key")706 t.Setenv("LOCAL_PROXY_API_KEY", "test-key")
707 path := filepath.Join(t.TempDir(), "models.json")707 path := filepath.Join(t.TempDir(), "models.json")
708 if err := os.WriteFile(path, []byte(`{708 if err := os.WriteFile(path, []byte(`{
@@ -734,7 +734,7 @@ func TestResolveCustomProviderInsecureFromModelsJSONBaseURL(t *testing.T) {
734 }734 }
735 735
736 func TestResolveOllamaFallsBackToDefaultBaseURL(t *testing.T) {736 func TestResolveOllamaFallsBackToDefaultBaseURL(t *testing.T) {
737- t.Setenv("ZOT_HOME", t.TempDir())737+ t.Setenv("ZUT_HOME", t.TempDir())
738 provider.SetLiveModels(nil)738 provider.SetLiveModels(nil)
739 defer provider.SetLiveModels(nil)739 defer provider.SetLiveModels(nil)
740 740
@@ -785,7 +785,7 @@ func TestResolveInsecureOnlyWithExplicitBaseURL(t *testing.T) {
785 orig := http.DefaultTransport785 orig := http.DefaultTransport
786 t.Cleanup(func() { http.DefaultTransport = orig })786 t.Cleanup(func() { http.DefaultTransport = orig })
787 787
788- t.Setenv("ZOT_HOME", t.TempDir())788+ t.Setenv("ZUT_HOME", t.TempDir())
789 t.Setenv("OPENAI_API_KEY", "test-key")789 t.Setenv("OPENAI_API_KEY", "test-key")
790 790
791 resolved, err := Resolve(Args{Provider: "moonshotai", InsecureTLS: true}, false)791 resolved, err := Resolve(Args{Provider: "moonshotai", InsecureTLS: true}, false)
@@ -811,7 +811,7 @@ func TestResolveInsecureFromConfigRequiresExplicitBaseURL(t *testing.T) {
811 orig := http.DefaultTransport811 orig := http.DefaultTransport
812 t.Cleanup(func() { http.DefaultTransport = orig })812 t.Cleanup(func() { http.DefaultTransport = orig })
813 813
814- t.Setenv("ZOT_HOME", t.TempDir())814+ t.Setenv("ZUT_HOME", t.TempDir())
815 t.Setenv("OPENAI_API_KEY", "test-key")815 t.Setenv("OPENAI_API_KEY", "test-key")
816 if err := SaveConfig(Config{Provider: "openai", Insecure: true}); err != nil {816 if err := SaveConfig(Config{Provider: "openai", Insecure: true}); err != nil {
817 t.Fatal(err)817 t.Fatal(err)
M packages/agent/changelog.go +1 -1
@@ -45,7 +45,7 @@ func FetchChangelog(ctx context.Context, version string) (ChangelogInfo, error)
45 if !strings.HasPrefix(tag, "v") {45 if !strings.HasPrefix(tag, "v") {
46 tag = "v" + tag46 tag = "v" + tag
47 }47 }
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)
49 req, err := http.NewRequestWithContext(ctx, "GET", url, nil)49 req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
50 if err != nil {50 if err != nil {
51 return ChangelogInfo{}, err51 return ChangelogInfo{}, err
M packages/agent/cli.go +45 -45
@@ -16,16 +16,16 @@ import (
16 "syscall"16 "syscall"
17 "time"17 "time"
18 18
19- "github.com/patriceckhart/zot/packages/agent/extensions"19+ "github.com/bnema/zut/packages/agent/extensions"
20- "github.com/patriceckhart/zot/packages/agent/extproto"20+ "github.com/bnema/zut/packages/agent/extproto"
21- "github.com/patriceckhart/zot/packages/agent/modes"21+ "github.com/bnema/zut/packages/agent/modes"
22- "github.com/patriceckhart/zot/packages/agent/skills"22+ "github.com/bnema/zut/packages/agent/skills"
23- "github.com/patriceckhart/zot/packages/agent/subagents"23+ "github.com/bnema/zut/packages/agent/subagents"
24- "github.com/patriceckhart/zot/packages/agent/tools"24+ "github.com/bnema/zut/packages/agent/tools"
25- "github.com/patriceckhart/zot/packages/core"25+ "github.com/bnema/zut/packages/core"
26- "github.com/patriceckhart/zot/packages/provider"26+ "github.com/bnema/zut/packages/provider"
27- "github.com/patriceckhart/zot/packages/provider/auth"27+ "github.com/bnema/zut/packages/provider/auth"
28- "github.com/patriceckhart/zot/packages/tui"28+ "github.com/bnema/zut/packages/tui"
29 )29 )
30 30
31 // interactiveExtHooks is a tiny adapter that lets the extension31 // interactiveExtHooks is a tiny adapter that lets the extension
@@ -471,7 +471,7 @@ func prepareSessionResumeWithOptions(path string, current *core.Agent, currentPr
471 }471 }
472 472
473 // Old sessions may have either field absent. Only a complete stored473 // 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.
475 if storedProvider == "" || storedModel == "" {475 if storedProvider == "" || storedModel == "" {
476 keepSession = true476 keepSession = true
477 return candidate, nil477 return candidate, nil
@@ -688,7 +688,7 @@ func fanoutAgentEvent(mgr *extensions.Manager, ev core.AgentEvent) {
688 }688 }
689 }689 }
690 690
691-// Run is the top-level entrypoint for the zot binary.691+// Run is the top-level entrypoint for the zut binary.
692 func Run(rawArgs []string, version string) error {692 func Run(rawArgs []string, version string) error {
693 // Extension installs can invoke external build and clone processes.693 // Extension installs can invoke external build and clone processes.
694 // Give those processes a cancellation context without changing signal694 // Give those processes a cancellation context without changing signal
@@ -700,7 +700,7 @@ func Run(rawArgs []string, version string) error {
700 }700 }
701 defer stopExtContext()701 defer stopExtContext()
702 702
703- // Subcommand router: `zot bot ...` is handled separately so the703+ // Subcommand router: `zut bot ...` is handled separately so the
704 // generic flag parser doesn't reject "bot" as a positional arg.704 // generic flag parser doesn't reject "bot" as a positional arg.
705 if handled, err := runBotCommand(rawArgs, version); handled {705 if handled, err := runBotCommand(rawArgs, version); handled {
706 return err706 return err
@@ -711,14 +711,14 @@ func Run(rawArgs []string, version string) error {
711 if handled, err := runUpdateCommand(rawArgs, version); handled {711 if handled, err := runUpdateCommand(rawArgs, version); handled {
712 return err712 return err
713 }713 }
714- if handled, err := runZotfileCommand(rawArgs, version); handled {714+ if handled, err := runZutfileCommand(rawArgs, version); handled {
715 return err715 return err
716 }716 }
717 return runWithArgsRaw(rawArgs, version)717 return runWithArgsRaw(rawArgs, version)
718 }718 }
719 719
720 func runWithArgsRaw(rawArgs []string, version string) error {720 func runWithArgsRaw(rawArgs []string, version string) error {
721- // `zot rpc` is shorthand for `zot --rpc` so third-party apps can721+ // `zut rpc` is shorthand for `zut --rpc` so third-party apps can
722 // spawn the binary with a clean argv. Strip the leading 'rpc'722 // spawn the binary with a clean argv. Strip the leading 'rpc'
723 // token and let the rest flow through the normal arg parser.723 // token and let the rest flow through the normal arg parser.
724 if len(rawArgs) > 0 && rawArgs[0] == "rpc" {724 if len(rawArgs) > 0 && rawArgs[0] == "rpc" {
@@ -735,7 +735,7 @@ func runWithArgsRaw(rawArgs []string, version string) error {
735 return nil735 return nil
736 }736 }
737 if args.Version {737 if args.Version {
738- fmt.Println("zot", version)738+ fmt.Println("zut", version)
739 return nil739 return nil
740 }740 }
741 // Model catalog: load any cached discovery data before we inspect741 // Model catalog: load any cached discovery data before we inspect
@@ -797,7 +797,7 @@ func (nonInteractiveExtHooks) ClearWidget(string, string)
797 // wire tools into the resolved registry, and a cleanup closure to797 // wire tools into the resolved registry, and a cleanup closure to
798 // defer. Mirrors the interactive-mode setup minus the TUI hooks.798 // defer. Mirrors the interactive-mode setup minus the TUI hooks.
799 func setupNonInteractiveExtensions(ctx context.Context, args Args, r *Resolved, version string) (*extensions.Manager, func()) {799 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{})
801 for _, e := range extMgr.LoadExplicit(ctx, args.Exts) {801 for _, e := range extMgr.LoadExplicit(ctx, args.Exts) {
802 fmt.Fprintln(os.Stderr, "extension load:", e)802 fmt.Fprintln(os.Stderr, "extension load:", e)
803 }803 }
@@ -925,7 +925,7 @@ func runPrintMode(ctx context.Context, args Args, version string) error {
925 }925 }
926 926
927 start := len(ag.Messages())927 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 {
929 return err929 return err
930 }930 }
931 if err := reloadResourcesAfterStartupPre(ctx, args, extMgr, r.Sandbox, ag); err != nil {931 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 {
991 991
992 start := len(ag.Messages())992 start := len(ag.Messages())
993 preSink, finishPre := newStreamTextSink(os.Stdout)993 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 {
995 finishPre()995 finishPre()
996 return err996 return err
997 }997 }
@@ -1098,7 +1098,7 @@ func runJSONMode(ctx context.Context, args Args, version string) error {
1098 preSink := func(ev core.AgentEvent) {1098 preSink := func(ev core.AgentEvent) {
1099 _ = enc.Encode(modes.EventToJSON(ev))1099 _ = enc.Encode(modes.EventToJSON(ev))
1100 }1100 }
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 {
1102 _ = enc.Encode(map[string]any{"type": "error", "message": err.Error()})1102 _ = enc.Encode(map[string]any{"type": "error", "message": err.Error()})
1103 return err1103 return err
1104 }1104 }
@@ -1111,11 +1111,11 @@ func runJSONMode(ctx context.Context, args Args, version string) error {
1111 return err1111 return err
1112 }1112 }
1113 1113
1114-// runZotfileStartupPre runs entry.pre before the main non-interactive prompt.1114+// runZutfileStartupPre runs entry.pre before the main non-interactive prompt.
1115 // "!command" values execute via BashTool; other values are sent as an agent turn.1115 // "!command" values execute via BashTool; other values are sent as an agent turn.
1116 // shellOut receives live shell chunks (typically os.Stderr); sink receives1116 // shellOut receives live shell chunks (typically os.Stderr); sink receives
1117 // agent events for plain-text pre (stream mode wires a live text sink).1117 // 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 {
1119 pre = strings.TrimSpace(pre)1119 pre = strings.TrimSpace(pre)
1120 if pre == "" {1120 if pre == "" {
1121 return nil1121 return nil
@@ -1143,7 +1143,7 @@ func runZotfileStartupPre(ctx context.Context, pre, cwd string, sandbox *tools.S
1143 bash := &tools.BashTool{CWD: cwd, Sandbox: sandbox}1143 bash := &tools.BashTool{CWD: cwd, Sandbox: sandbox}
1144 res, err := bash.Execute(ctx, raw, progress)1144 res, err := bash.Execute(ctx, raw, progress)
1145 if err != nil {1145 if err != nil {
1146- return fmt.Errorf("zotfile entry.pre: %w", err)1146+ return fmt.Errorf("zutfile entry.pre: %w", err)
1147 }1147 }
1148 if res.IsError {1148 if res.IsError {
1149 var sb strings.Builder1149 var sb strings.Builder
@@ -1156,12 +1156,12 @@ func runZotfileStartupPre(ctx context.Context, pre, cwd string, sandbox *tools.S
1156 if msg == "" {1156 if msg == "" {
1157 msg = "command failed"1157 msg = "command failed"
1158 }1158 }
1159- return fmt.Errorf("zotfile entry.pre: %s", msg)1159+ return fmt.Errorf("zutfile entry.pre: %s", msg)
1160 }1160 }
1161 return nil1161 return nil
1162 }1162 }
1163 if ag == nil {1163 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")
1165 }1165 }
1166 if sink == nil {1166 if sink == nil {
1167 sink = func(core.AgentEvent) {}1167 sink = func(core.AgentEvent) {}
@@ -1174,7 +1174,7 @@ var errInteractiveAgentChanged = errors.New("interactive agent changed during pr
1174 // refreshAgentToolsAndPrompt re-resolves tools (including rediscovered1174 // refreshAgentToolsAndPrompt re-resolves tools (including rediscovered
1175 // skills and currently loaded extension tools) and updates the live1175 // skills and currently loaded extension tools) and updates the live
1176 // agent's registry and system prompt. Used after /reload-ext and after1176 // 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.
1178 // mutateRegistry, if non-nil, can inject session-specific tools (e.g. subagent_spawn).1178 // mutateRegistry, if non-nil, can inject session-specific tools (e.g. subagent_spawn).
1179 // interactive, when non-nil, serializes the final commit with agent replacement.1179 // interactive, when non-nil, serializes the final commit with agent replacement.
1180 func refreshAgentToolsAndPrompt(args Args, sharedSandbox *tools.Sandbox, extToolAdapter ExtensionToolSource, ag *core.Agent, mutateRegistry func(core.Registry) core.Registry, interactive *modes.Interactive) error {1180 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 {
1289 // host after constructing it below so startup alerts can be buffered.1289 // host after constructing it below so startup alerts can be buffered.
1290 var iv *modes.Interactive1290 var iv *modes.Interactive
1291 extHooks := &interactiveExtHooks{}1291 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)
1293 var startupExtensionErrors []string1293 var startupExtensionErrors []string
1294 // --ext paths first so they win against installed extensions of1294 // --ext paths first so they win against installed extensions of
1295 // the same name (loadOne's first-write-wins semantics).1295 // the same name (loadOne's first-write-wins semantics).
@@ -1321,7 +1321,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
1321 1321
1322 // Build the subagent supervisor BEFORE the agent so the auto-subagents1322 // Build the subagent supervisor BEFORE the agent so the auto-subagents
1323 // tool can reference it during tool-registry construction. State1323 // tool can reference it during tool-registry construction. State
1324- // lives under ZotHome/subagents so per-agent meta/events survive1324+ // lives under ZutHome/subagents so per-agent meta/events survive
1325 // restarts; the user can hunt orphaned agents down with1325 // restarts; the user can hunt orphaned agents down with
1326 // `git worktree list` if anything misbehaves.1326 // `git worktree list` if anything misbehaves.
1327 //1327 //
@@ -1331,7 +1331,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
1331 var subagentsMgr *subagents.Supervisor1331 var subagentsMgr *subagents.Supervisor
1332 subagentsMgr = subagents.New(subagents.Config{1332 subagentsMgr = subagents.New(subagents.Config{
1333 Context: ctx,1333 Context: ctx,
1334- Root: filepath.Join(ZotHome(), "subagents"),1334+ Root: filepath.Join(ZutHome(), "subagents"),
1335 RepoRoot: r.CWD,1335 RepoRoot: r.CWD,
1336 Provider: r.Provider,1336 Provider: r.Provider,
1337 FastMode: r.FastMode,1337 FastMode: r.FastMode,
@@ -1375,7 +1375,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
1375 // shows them as detached and the user can resume / remove them.1375 // shows them as detached and the user can resume / remove them.
1376 _, _ = subagentsMgr.Reload()1376 _, _ = subagentsMgr.Reload()
1377 if args.PermissionSet != nil {1377 if args.PermissionSet != nil {
1378- // Packaged zotfile agents have an explicit capability ceiling. A1378+ // Packaged zutfile agents have an explicit capability ceiling. A
1379 // child process would otherwise rebuild a fresh unrestricted sandbox,1379 // child process would otherwise rebuild a fresh unrestricted sandbox,
1380 // so do not expose either slash or tool-based delegation from this1380 // so do not expose either slash or tool-based delegation from this
1381 // restricted host until worker capability propagation exists.1381 // restricted host until worker capability propagation exists.
@@ -1559,7 +1559,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
1559 // rescue (a bad key, a typo'd base URL, or a corporate gateway that1559 // rescue (a bad key, a typo'd base URL, or a corporate gateway that
1560 // only the originally-picked provider needed). Re-resolving without1560 // only the originally-picked provider needed). Re-resolving without
1561 // them lets the rescue retry use env vars / auth.json / provider1561 // 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.
1563 buildAgentForRescue := func(providerOverride, modelOverride string) (*core.Agent, string, string, error) {1563 buildAgentForRescue := func(providerOverride, modelOverride string) (*core.Agent, string, string, error) {
1564 next := args1564 next := args
1565 next.APIKey = ""1565 next.APIKey = ""
@@ -1961,10 +1961,10 @@ func runInteractive(ctx context.Context, args Args, version string) error {
1961 // Fresh session in the new cwd's bucket. We bypass1961 // Fresh session in the new cwd's bucket. We bypass
1962 // openOrCreateSession's --continue / --resume branches1962 // openOrCreateSession's --continue / --resume branches
1963 // because /cd's semantics are "start fresh here", matching1963 // 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.
1965 var newSess *core.Session1965 var newSess *core.Session
1966 if !args.NoSess {1966 if !args.NoSess {
1967- newRoot := agentSessionsRoot(ZotHome(), args)1967+ newRoot := agentSessionsRoot(ZutHome(), args)
1968 core.PruneEmptySessions(newRoot, absPath)1968 core.PruneEmptySessions(newRoot, absPath)
1969 var serr error1969 var serr error
1970 newSess, serr = core.NewSession(newRoot, absPath, newProvider, newModel, version)1970 newSess, serr = core.NewSession(newRoot, absPath, newProvider, newModel, version)
@@ -2004,7 +2004,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
2004 2004
2005 // Push the new state into the running Interactive.2005 // Push the new state into the running Interactive.
2006 if iv != nil {2006 if iv != nil {
2007- startupPaths := instructionContextPaths(loadAgentsContext(absPath, ZotHome()))2007+ startupPaths := instructionContextPaths(loadAgentsContext(absPath, ZutHome()))
2008 iv.ApplyChangedCWDWithStartupContext(newAg, newProvider, newModel, absPath, startupPaths)2008 iv.ApplyChangedCWDWithStartupContext(newAg, newProvider, newModel, absPath, startupPaths)
2009 }2009 }
2010 2010
@@ -2020,7 +2020,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
2020 2020
2021 devBuild := isDevVersion(version)2021 devBuild := isDevVersion(version)
2022 if devBuild {2022 if devBuild {
2023- fmt.Fprintln(os.Stderr, "zot:", devVersionNotice)2023+ fmt.Fprintln(os.Stderr, "zut:", devVersionNotice)
2024 }2024 }
2025 term := tui.NewProcTerm()2025 term := tui.NewProcTerm()
2026 2026
@@ -2033,7 +2033,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
2033 updateCh = make(chan modes.UpdateInfo, 1)2033 updateCh = make(chan modes.UpdateInfo, 1)
2034 go func() {2034 go func() {
2035 defer close(updateCh)2035 defer close(updateCh)
2036- src := <-CheckForUpdateAsync(ZotHome(), version)2036+ src := <-CheckForUpdateAsync(ZutHome(), version)
2037 updateCh <- modes.UpdateInfo{2037 updateCh <- modes.UpdateInfo{
2038 Current: src.Current,2038 Current: src.Current,
2039 Latest: src.Latest,2039 Latest: src.Latest,
@@ -2076,10 +2076,10 @@ func runInteractive(ctx context.Context, args Args, version string) error {
2076 for idx, s := range initialCfg.QuickModelShortcuts {2076 for idx, s := range initialCfg.QuickModelShortcuts {
2077 quickModelShortcuts[idx] = modes.QuickModelShortcut{Provider: s.Provider, Model: s.Model}2077 quickModelShortcuts[idx] = modes.QuickModelShortcut{Provider: s.Provider, Model: s.Model}
2078 }2078 }
2079- theme, _, themeErr := tui.DetectThemeWithCustom(ZotHome(), initialCfg.Theme, 80*time.Millisecond)2079+ theme, _, themeErr := tui.DetectThemeWithCustom(ZutHome(), initialCfg.Theme, 80*time.Millisecond)
2080 if themeErr != nil {2080 if themeErr != nil {
2081 fmt.Fprintln(os.Stderr, "theme load:", themeErr)2081 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) {
2083 initialCfg.Theme = ""2083 initialCfg.Theme = ""
2084 _ = SaveConfig(initialCfg)2084 _ = SaveConfig(initialCfg)
2085 }2085 }
@@ -2095,7 +2095,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
2095 subagentsMgr.SetActiveSession(sess.ID)2095 subagentsMgr.SetActiveSession(sess.ID)
2096 }2096 }
2097 // Best-effort shutdown on interactive exit: stop all running2097 // 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.
2099 if subagentsMgr != nil {2099 if subagentsMgr != nil {
2100 defer subagentsMgr.StopAll()2100 defer subagentsMgr.StopAll()
2101 }2101 }
@@ -2154,8 +2154,8 @@ func runInteractive(ctx context.Context, args Args, version string) error {
2154 StartupExtensionErrors: startupExtensionErrors,2154 StartupExtensionErrors: startupExtensionErrors,
2155 StartupSkillNames: startupSkillNames(startupSkills),2155 StartupSkillNames: startupSkillNames(startupSkills),
2156 ShowInstructionsAtStartup: initialCfg.ShowInstructionsAtStartup,2156 ShowInstructionsAtStartup: initialCfg.ShowInstructionsAtStartup,
2157- ZotHome: ZotHome(),2157+ ZutHome: ZutHome(),
2158- SessionsRoot: agentSessionsRoot(ZotHome(), args),2158+ SessionsRoot: agentSessionsRoot(ZutHome(), args),
2159 Version: version,2159 Version: version,
2160 UpdateInfoChan: updateCh,2160 UpdateInfoChan: updateCh,
2161 Sandbox: sharedSandbox,2161 Sandbox: sharedSandbox,
@@ -2281,7 +2281,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
2281 // model still sees them through the system-prompt2281 // model still sees them through the system-prompt
2282 // manifest and the skill tool.2282 // manifest and the skill tool.
2283 userHome, _ := os.UserHomeDir()2283 userHome, _ := os.UserHomeDir()
2284- list, _ := skills.Discover(ZotHome(), r.CWD, userHome, args.WithSkills)2284+ list, _ := skills.Discover(ZutHome(), r.CWD, userHome, args.WithSkills)
2285 list = mergeExtensionSkills(skills.NewTool(list), extMgr.Skills())2285 list = mergeExtensionSkills(skills.NewTool(list), extMgr.Skills())
2286 return skills.VisibleSkills(list)2286 return skills.VisibleSkills(list)
2287 },2287 },
@@ -2345,7 +2345,7 @@ func runInteractive(ctx context.Context, args Args, version string) error {
2345 })2345 })
2346 }2346 }
2347 2347
2348- // Signal-driven flush: a SIGTERM / SIGHUP to the zot process2348+ // Signal-driven flush: a SIGTERM / SIGHUP to the zut process
2349 // (closed terminal window, system shutdown, kill) used to lose2349 // (closed terminal window, system shutdown, kill) used to lose
2350 // the entire in-memory transcript because the deferred post-Run2350 // the entire in-memory transcript because the deferred post-Run
2351 // flush below never ran. Per-message persistence above covers2351 // flush below never ran. Per-message persistence above covers
@@ -2419,10 +2419,10 @@ func openOrCreateSession(args Args, r Resolved, ag *core.Agent, version string)
2419 if args.NoSess {2419 if args.NoSess {
2420 return nil, nil2420 return nil, nil
2421 }2421 }
2422- // Sweep meta-only files left over from older zot versions (and from2422+ // Sweep meta-only files left over from older zut versions (and from
2423 // any session that crashed before its first AppendMessage). Cheap;2423 // any session that crashed before its first AppendMessage). Cheap;
2424 // reads the first few bytes of each file in the cwd's session dir.2424 // reads the first few bytes of each file in the cwd's session dir.
2425- sessionsRoot := agentSessionsRoot(ZotHome(), args)2425+ sessionsRoot := agentSessionsRoot(ZutHome(), args)
2426 core.PruneEmptySessions(sessionsRoot, args.CWD)2426 core.PruneEmptySessions(sessionsRoot, args.CWD)
2427 var (2427 var (
2428 s *core.Session2428 s *core.Session
M packages/agent/cli_alert_test.go +2 -2
@@ -3,8 +3,8 @@ package agent
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/agent/extproto"6+ "github.com/bnema/zut/packages/agent/extproto"
7- "github.com/patriceckhart/zot/packages/agent/modes"7+ "github.com/bnema/zut/packages/agent/modes"
8 )8 )
9 9
10 func TestInteractiveExtHooksBufferStartupChrome(t *testing.T) {10 func TestInteractiveExtHooksBufferStartupChrome(t *testing.T) {
M packages/agent/cli_session_test.go +4 -4
@@ -9,9 +9,9 @@ import (
9 "testing"9 "testing"
10 "time"10 "time"
11 11
12- "github.com/patriceckhart/zot/packages/agent/modes"12+ "github.com/bnema/zut/packages/agent/modes"
13- "github.com/patriceckhart/zot/packages/core"13+ "github.com/bnema/zut/packages/core"
14- "github.com/patriceckhart/zot/packages/provider"14+ "github.com/bnema/zut/packages/provider"
15 )15 )
16 16
17 func TestTrimMessagesForResumeCarriesDeferredToolActivation(t *testing.T) {17 func TestTrimMessagesForResumeCarriesDeferredToolActivation(t *testing.T) {
@@ -97,7 +97,7 @@ func TestLiveInteractiveAgentFallsBackBeforeInteractiveConstruction(t *testing.T
97 }97 }
98 98
99 func TestPersistModelCallbackDoesNotReenterSessionTransition(t *testing.T) {99 func TestPersistModelCallbackDoesNotReenterSessionTransition(t *testing.T) {
100- t.Setenv("ZOT_HOME", t.TempDir())100+ t.Setenv("ZUT_HOME", t.TempDir())
101 sess, err := core.NewSession(t.TempDir(), t.TempDir(), "old-provider", "old-model", "test")101 sess, err := core.NewSession(t.TempDir(), t.TempDir(), "old-provider", "old-model", "test")
102 if err != nil {102 if err != nil {
103 t.Fatal(err)103 t.Fatal(err)
M packages/agent/config.go +25 -25
@@ -12,8 +12,8 @@ import (
12 "strings"12 "strings"
13 "time"13 "time"
14 14
15- providerpkg "github.com/patriceckhart/zot/packages/provider"15+ providerpkg "github.com/bnema/zut/packages/provider"
16- "github.com/patriceckhart/zot/packages/provider/auth"16+ "github.com/bnema/zut/packages/provider/auth"
17 )17 )
18 18
19 // QuickModelShortcut is one configured keyboard shortcut slot.19 // QuickModelShortcut is one configured keyboard shortcut slot.
@@ -57,13 +57,13 @@ type Config struct {
57 // ToolRender selects how tool calls are drawn in interactive mode.57 // ToolRender selects how tool calls are drawn in interactive mode.
58 // "box" (default, or empty) wraps each call in a bordered panel;58 // "box" (default, or empty) wraps each call in a bordered panel;
59 // "flat" drops the frame for a quiet header line plus indented,59 // "flat" drops the frame for a quiet header line plus indented,
60- // frameless output. The ZOT_FLAT_TOOLS env var overrides this when60+ // frameless output. The ZUT_FLAT_TOOLS env var overrides this when
61 // set ("1"/"true" forces flat, "0"/"false" forces box).61 // set ("1"/"true" forces flat, "0"/"false" forces box).
62 ToolRender string `json:"tool_render,omitempty"`62 ToolRender string `json:"tool_render,omitempty"`
63 63
64 // CompactInput renders sent user messages as a single quiet gutter64 // CompactInput renders sent user messages as a single quiet gutter
65 // line instead of a padded, background-tinted bubble. nil/false65 // line instead of a padded, background-tinted bubble. nil/false
66- // (the default) keeps the bubble. The ZOT_COMPACT_INPUT env var66+ // (the default) keeps the bubble. The ZUT_COMPACT_INPUT env var
67 // overrides this when set.67 // overrides this when set.
68 CompactInput *bool `json:"compact_input,omitempty"`68 CompactInput *bool `json:"compact_input,omitempty"`
69 69
@@ -71,7 +71,7 @@ type Config struct {
71 // Ctrl+1..9. Cmd+1..9 may also work on terminals that forward Super.71 // Ctrl+1..9. Cmd+1..9 may also work on terminals that forward Super.
72 QuickModelShortcuts []QuickModelShortcut `json:"quick_model_shortcuts,omitempty"`72 QuickModelShortcuts []QuickModelShortcut `json:"quick_model_shortcuts,omitempty"`
73 73
74- // InlineImagesEnabled controls whether zot draws screenshots inline74+ // InlineImagesEnabled controls whether zut draws screenshots inline
75 // when the terminal supports an image protocol. nil/missing means75 // when the terminal supports an image protocol. nil/missing means
76 // auto (enabled when supported); false disables; true forces the76 // auto (enabled when supported); false disables; true forces the
77 // detected protocol when available.77 // detected protocol when available.
@@ -174,43 +174,43 @@ type Config struct {
174 LastChangelogShown string `json:"last_changelog_shown,omitempty"`174 LastChangelogShown string `json:"last_changelog_shown,omitempty"`
175 }175 }
176 176
177-// ZotHome returns $ZOT_HOME or the OS-default data dir.177+// ZutHome returns $ZUT_HOME or the OS-default data dir.
178 //178 //
179-// All zot state (config.json, auth.json, sessions/, logs/) lives under179+// All zut state (config.json, auth.json, sessions/, logs/) lives under
180 // this directory.180 // this directory.
181-func ZotHome() string {181+func ZutHome() string {
182- if v := os.Getenv("ZOT_HOME"); v != "" {182+ if v := os.Getenv("ZUT_HOME"); v != "" {
183 return v183 return v
184 }184 }
185 if v := os.Getenv("XDG_STATE_HOME"); v != "" {185 if v := os.Getenv("XDG_STATE_HOME"); v != "" {
186- return filepath.Join(v, "zot")186+ return filepath.Join(v, "zut")
187 }187 }
188 switch runtime.GOOS {188 switch runtime.GOOS {
189 case "darwin":189 case "darwin":
190 if home, err := os.UserHomeDir(); err == nil {190 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")
192 }192 }
193 case "windows":193 case "windows":
194 if v := os.Getenv("LOCALAPPDATA"); v != "" {194 if v := os.Getenv("LOCALAPPDATA"); v != "" {
195- return filepath.Join(v, "zot")195+ return filepath.Join(v, "zut")
196 }196 }
197 }197 }
198 if home, err := os.UserHomeDir(); err == nil {198 if home, err := os.UserHomeDir(); err == nil {
199- return filepath.Join(home, ".local", "state", "zot")199+ return filepath.Join(home, ".local", "state", "zut")
200 }200 }
201- return ".zot"201+ return ".zut"
202 }202 }
203 203
204 // ConfigPath returns the path to config.json.204 // 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") }
206 206
207 // FlatToolRender reports whether tool calls should render flat (no207 // FlatToolRender reports whether tool calls should render flat (no
208-// bordered panel). The ZOT_FLAT_TOOLS env var takes precedence over208+// bordered panel). The ZUT_FLAT_TOOLS env var takes precedence over
209 // the config when set: "1"/"true"/"yes"/"on" force flat, "0"/"false"/209 // the config when set: "1"/"true"/"yes"/"on" force flat, "0"/"false"/
210 // "no"/"off" force box. Otherwise the config's tool_render is210 // "no"/"off" force box. Otherwise the config's tool_render is
211 // consulted; "flat" is flat, anything else (including empty) is box.211 // consulted; "flat" is flat, anything else (including empty) is box.
212 func (c Config) FlatToolRender() bool {212 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 != "" {
214 switch v {214 switch v {
215 case "1", "true", "yes", "on", "flat":215 case "1", "true", "yes", "on", "flat":
216 return true216 return true
@@ -223,12 +223,12 @@ func (c Config) FlatToolRender() bool {
223 223
224 // CompactUserInput reports whether sent user messages should render as224 // CompactUserInput reports whether sent user messages should render as
225 // a single quiet gutter line instead of a padded, background-tinted225 // a single quiet gutter line instead of a padded, background-tinted
226-// bubble. The ZOT_COMPACT_INPUT env var takes precedence over the226+// bubble. The ZUT_COMPACT_INPUT env var takes precedence over the
227 // config when set: "1"/"true"/"yes"/"on" force compact, "0"/"false"/227 // config when set: "1"/"true"/"yes"/"on" force compact, "0"/"false"/
228 // "no"/"off" force the bubble. Otherwise the config's compact_input228 // "no"/"off" force the bubble. Otherwise the config's compact_input
229 // is consulted (nil/false means the bubble).229 // is consulted (nil/false means the bubble).
230 func (c Config) CompactUserInput() bool {230 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 != "" {
232 switch v {232 switch v {
233 case "1", "true", "yes", "on", "compact":233 case "1", "true", "yes", "on", "compact":
234 return true234 return true
@@ -269,19 +269,19 @@ func (c Config) LSPDiagnosticsOnEditEnabled(subagent bool) bool {
269 }269 }
270 270
271 // AuthPath returns the path to auth.json.271 // 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") }
273 273
274 // KimiCLIFallbackDisabledPath returns a sentinel that disables falling274 // 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`.
276 func KimiCLIFallbackDisabledPath() string {276 func KimiCLIFallbackDisabledPath() string {
277- return filepath.Join(ZotHome(), "kimi-cli-fallback-disabled")277+ return filepath.Join(ZutHome(), "kimi-cli-fallback-disabled")
278 }278 }
279 279
280 // SessionsPath returns the directory holding session files.280 // 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") }
282 282
283 // LogsPath returns the directory holding log files.283 // 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") }
285 285
286 // LoadConfig reads the config file, returning defaults if missing.286 // LoadConfig reads the config file, returning defaults if missing.
287 func LoadConfig() (Config, error) {287 func LoadConfig() (Config, error) {
@@ -301,7 +301,7 @@ func LoadConfig() (Config, error) {
301 301
302 // SaveConfig writes the config file, creating parent dirs.302 // SaveConfig writes the config file, creating parent dirs.
303 func SaveConfig(c Config) error {303 func SaveConfig(c Config) error {
304- if err := os.MkdirAll(ZotHome(), 0o755); err != nil {304+ if err := os.MkdirAll(ZutHome(), 0o755); err != nil {
305 return err305 return err
306 }306 }
307 b, err := json.MarshalIndent(c, "", " ")307 b, err := json.MarshalIndent(c, "", " ")
M packages/agent/config_command_test.go +4 -4
@@ -9,11 +9,11 @@ import (
9 "strings"9 "strings"
10 "testing"10 "testing"
11 11
12- "github.com/patriceckhart/zot/packages/provider/auth"12+ "github.com/bnema/zut/packages/provider/auth"
13 )13 )
14 14
15 func TestAgentAPIKeyCommandHelperProcess(t *testing.T) {15 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" {
17 return17 return
18 }18 }
19 args := os.Args19 args := os.Args
@@ -32,9 +32,9 @@ func TestAgentAPIKeyCommandHelperProcess(t *testing.T) {
32 32
33 func TestCommandCredentialIsLazy(t *testing.T) {33 func TestCommandCredentialIsLazy(t *testing.T) {
34 home := t.TempDir()34 home := t.TempDir()
35- t.Setenv("ZOT_HOME", home)35+ t.Setenv("ZUT_HOME", home)
36 t.Setenv("LAZY_PROVIDER_API_KEY", "")36 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")
38 marker := filepath.Join(home, "command-ran")38 marker := filepath.Join(home, "command-ran")
39 credentials := auth.Credentials{AdditionalAPIKeyCreds: map[string]auth.ProviderCreds{39 credentials := auth.Credentials{AdditionalAPIKeyCreds: map[string]auth.ProviderCreds{
40 "lazy-provider": {40 "lazy-provider": {
M packages/agent/config_gcp_test.go +6 -6
@@ -5,7 +5,7 @@ import (
5 "path/filepath"5 "path/filepath"
6 "testing"6 "testing"
7 7
8- providerauth "github.com/patriceckhart/zot/packages/provider/auth"8+ providerauth "github.com/bnema/zut/packages/provider/auth"
9 )9 )
10 10
11 func isolateGoogleVertexADC(t *testing.T) string {11 func isolateGoogleVertexADC(t *testing.T) string {
@@ -24,7 +24,7 @@ func isolateGoogleVertexADC(t *testing.T) string {
24 // takes top priority for the google-vertex provider and is returned with24 // takes top priority for the google-vertex provider and is returned with
25 // method "apikey".25 // method "apikey".
26 func TestResolveCredentialGoogleVertexAPIKey(t *testing.T) {26 func TestResolveCredentialGoogleVertexAPIKey(t *testing.T) {
27- t.Setenv("ZOT_HOME", t.TempDir())27+ t.Setenv("ZUT_HOME", t.TempDir())
28 isolateGoogleVertexADC(t)28 isolateGoogleVertexADC(t)
29 t.Setenv("GOOGLE_CLOUD_API_KEY", "gcp-key-123")29 t.Setenv("GOOGLE_CLOUD_API_KEY", "gcp-key-123")
30 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")30 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
@@ -46,7 +46,7 @@ func TestResolveCredentialGoogleVertexAPIKey(t *testing.T) {
46 // resolution returns the "<adc>" sentinel with method "apikey", matching46 // resolution returns the "<adc>" sentinel with method "apikey", matching
47 // the real NewVertex client's ADC-based auth flow.47 // the real NewVertex client's ADC-based auth flow.
48 func TestResolveCredentialGoogleVertexApplicationCredentialsEnv(t *testing.T) {48 func TestResolveCredentialGoogleVertexApplicationCredentialsEnv(t *testing.T) {
49- t.Setenv("ZOT_HOME", t.TempDir())49+ t.Setenv("ZUT_HOME", t.TempDir())
50 isolateGoogleVertexADC(t)50 isolateGoogleVertexADC(t)
51 t.Setenv("GOOGLE_CLOUD_API_KEY", "")51 t.Setenv("GOOGLE_CLOUD_API_KEY", "")
52 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/service-account.json")52 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/service-account.json")
@@ -67,7 +67,7 @@ func TestResolveCredentialGoogleVertexApplicationCredentialsEnv(t *testing.T) {
67 // env vars, the presence of the platform-specific default gcloud ADC file is67 // env vars, the presence of the platform-specific default gcloud ADC file is
68 // detected and resolves to the "<adc>" sentinel.68 // detected and resolves to the "<adc>" sentinel.
69 func TestResolveCredentialGoogleVertexDefaultADCFile(t *testing.T) {69 func TestResolveCredentialGoogleVertexDefaultADCFile(t *testing.T) {
70- t.Setenv("ZOT_HOME", t.TempDir())70+ t.Setenv("ZUT_HOME", t.TempDir())
71 adcPath := isolateGoogleVertexADC(t)71 adcPath := isolateGoogleVertexADC(t)
72 t.Setenv("GOOGLE_CLOUD_API_KEY", "")72 t.Setenv("GOOGLE_CLOUD_API_KEY", "")
73 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")73 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
@@ -95,7 +95,7 @@ func TestResolveCredentialGoogleVertexDefaultADCFile(t *testing.T) {
95 // wins over GOOGLE_APPLICATION_CREDENTIALS and the default ADC file when95 // wins over GOOGLE_APPLICATION_CREDENTIALS and the default ADC file when
96 // multiple credential sources are present simultaneously.96 // multiple credential sources are present simultaneously.
97 func TestResolveCredentialGoogleVertexPrecedence(t *testing.T) {97 func TestResolveCredentialGoogleVertexPrecedence(t *testing.T) {
98- t.Setenv("ZOT_HOME", t.TempDir())98+ t.Setenv("ZUT_HOME", t.TempDir())
99 adcPath := isolateGoogleVertexADC(t)99 adcPath := isolateGoogleVertexADC(t)
100 t.Setenv("GOOGLE_CLOUD_API_KEY", "explicit-key")100 t.Setenv("GOOGLE_CLOUD_API_KEY", "explicit-key")
101 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/sa.json")101 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/sa.json")
@@ -124,7 +124,7 @@ func TestResolveCredentialGoogleVertexPrecedence(t *testing.T) {
124 // auth.json entry), resolution fails with an error rather than silently124 // auth.json entry), resolution fails with an error rather than silently
125 // returning an empty credential.125 // returning an empty credential.
126 func TestResolveCredentialGoogleVertexNoCredentials(t *testing.T) {126 func TestResolveCredentialGoogleVertexNoCredentials(t *testing.T) {
127- t.Setenv("ZOT_HOME", t.TempDir())127+ t.Setenv("ZUT_HOME", t.TempDir())
128 isolateGoogleVertexADC(t)128 isolateGoogleVertexADC(t)
129 t.Setenv("GOOGLE_CLOUD_API_KEY", "")129 t.Setenv("GOOGLE_CLOUD_API_KEY", "")
130 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")130 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
M packages/agent/config_home_test.go +8 -8
@@ -5,20 +5,20 @@ import (
5 "testing"5 "testing"
6 )6 )
7 7
8-func TestZotHomePrefersExplicitHomeOverXDGStateHome(t *testing.T) {8+func TestZutHomePrefersExplicitHomeOverXDGStateHome(t *testing.T) {
9- t.Setenv("ZOT_HOME", filepath.Join("explicit", "zot"))9+ t.Setenv("ZUT_HOME", filepath.Join("explicit", "zut"))
10 t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))10 t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))
11 11
12- if got, want := ZotHome(), filepath.Join("explicit", "zot"); got != want {12+ if got, want := ZutHome(), filepath.Join("explicit", "zut"); got != want {
13- t.Fatalf("ZotHome() = %q, want %q", got, want)13+ t.Fatalf("ZutHome() = %q, want %q", got, want)
14 }14 }
15 }15 }
16 16
17-func TestZotHomeUsesXDGStateHome(t *testing.T) {17+func TestZutHomeUsesXDGStateHome(t *testing.T) {
18- t.Setenv("ZOT_HOME", "")18+ t.Setenv("ZUT_HOME", "")
19 t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))19 t.Setenv("XDG_STATE_HOME", filepath.Join("xdg", "state"))
20 20
21- if got, want := ZotHome(), filepath.Join("xdg", "state", "zot"); got != want {21+ if got, want := ZutHome(), filepath.Join("xdg", "state", "zut"); got != want {
22- t.Fatalf("ZotHome() = %q, want %q", got, want)22+ t.Fatalf("ZutHome() = %q, want %q", got, want)
23 }23 }
24 }24 }
M packages/agent/confirmation_events.go +2 -2
@@ -1,8 +1,8 @@
1 package agent1 package agent
2 2
3 import (3 import (
4- "github.com/patriceckhart/zot/packages/agent/extproto"4+ "github.com/bnema/zut/packages/agent/extproto"
5- "github.com/patriceckhart/zot/packages/core"5+ "github.com/bnema/zut/packages/core"
6 )6 )
7 7
8 // confirmationEventConfirmer decorates an interactive confirmer with an8 // confirmationEventConfirmer decorates an interactive confirmer with an
M packages/agent/confirmation_events_test.go +2 -2
@@ -4,8 +4,8 @@ import (
4 "reflect"4 "reflect"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/agent/extproto"7+ "github.com/bnema/zut/packages/agent/extproto"
8- "github.com/patriceckhart/zot/packages/core"8+ "github.com/bnema/zut/packages/core"
9 )9 )
10 10
11 type rememberingDetailedConfirmer struct {11 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.
2 //2 //
3-// An extension is a subprocess that talks to zot over its stdin/stdout3+// An extension is a subprocess that talks to zut over its stdin/stdout
4 // in newline-delimited JSON. This package wraps the wire format so4 // in newline-delimited JSON. This package wraps the wire format so
5 // extension authors can write straightforward Go without reimplementing5 // extension authors can write straightforward Go without reimplementing
6 // the protocol.6 // the protocol.
@@ -9,7 +9,7 @@
9 //9 //
10 // package main10 // package main
11 //11 //
12-// import "github.com/patriceckhart/zot/packages/agent/ext"12+// import "github.com/bnema/zut/packages/agent/ext"
13 //13 //
14 // func main() {14 // func main() {
15 // ext := ext.New("hello", "1.0.0")15 // ext := ext.New("hello", "1.0.0")
@@ -20,10 +20,10 @@
20 // }20 // }
21 //21 //
22 // Build it, drop the binary + an extension.json next to it under22 // 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.
24 //24 //
25 // Use OnHello when registration or configuration depends on host metadata25 // 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.
27 // Run sends hello, waits for hello_ack, runs OnHello, announces registrations,27 // Run sends hello, waits for hello_ack, runs OnHello, announces registrations,
28 // then sends ready.28 // then sends ready.
29 //29 //
@@ -40,7 +40,7 @@ import (
40 "os"40 "os"
41 "sync"41 "sync"
42 42
43- "github.com/patriceckhart/zot/packages/agent/extproto"43+ "github.com/bnema/zut/packages/agent/extproto"
44 )44 )
45 45
46 func base64Encode(b []byte) string { return base64.StdEncoding.EncodeToString(b) }46 func base64Encode(b []byte) string { return base64.StdEncoding.EncodeToString(b) }
@@ -48,13 +48,13 @@ func base64Encode(b []byte) string { return base64.StdEncoding.EncodeToString(b)
48 // CommandHandler is invoked when the user runs the extension's48 // CommandHandler is invoked when the user runs the extension's
49 // registered slash command. args is everything the user typed after49 // registered slash command. args is everything the user typed after
50 // the command name (already trimmed). Return a Response describing50 // the command name (already trimmed). Return a Response describing
51-// what zot should do next.51+// what zut should do next.
52 type CommandHandler func(args string) Response52 type CommandHandler func(args string) Response
53 53
54 // ToolHandler is invoked when the LLM calls a tool the extension54 // ToolHandler is invoked when the LLM calls a tool the extension
55 // registered. args is the raw JSON object the model produced; the55 // registered. args is the raw JSON object the model produced; the
56 // handler is responsible for parsing/validating it. Return a56 // 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.
58 type ToolHandler func(args json.RawMessage) ToolResult58 type ToolHandler func(args json.RawMessage) ToolResult
59 59
60 // EventHandler is called for each lifecycle event the extension60 // EventHandler is called for each lifecycle event the extension
@@ -63,7 +63,7 @@ type ToolHandler func(args json.RawMessage) ToolResult
63 // worker.63 // worker.
64 type EventHandler func(ev Event)64 type EventHandler func(ev Event)
65 65
66-// Event is a lifecycle notification from zot. The fields populated66+// Event is a lifecycle notification from zut. The fields populated
67 // depend on Name (the host's event_name string):67 // depend on Name (the host's event_name string):
68 //68 //
69 // session_start : Session69 // session_start : Session
@@ -221,7 +221,7 @@ func TextErrorResult(s string) ToolResult {
221 return ToolResult{Content: []ToolContent{Text(s)}, IsError: true}221 return ToolResult{Content: []ToolContent{Text(s)}, IsError: true}
222 }222 }
223 223
224-// Response tells zot how to react to a command invocation. Construct224+// Response tells zut how to react to a command invocation. Construct
225 // one with Prompt(), Insert(), Display(), or Noop().225 // one with Prompt(), Insert(), Display(), or Noop().
226 type Panel struct {226 type Panel struct {
227 ID string227 ID string
@@ -254,7 +254,7 @@ func Insert(text string) Response { return Response{Action: "insert", Insert: te
254 func Display(text string) Response { return Response{Action: "display", Display: text} }254 func Display(text string) Response { return Response{Action: "display", Display: text} }
255 255
256 // OpenPanel returns a Response that opens an interactive extension-owned256 // OpenPanel returns a Response that opens an interactive extension-owned
257-// panel inside zot.257+// panel inside zut.
258 func OpenPanel(id, title string, lines []string, footer string) Response {258 func OpenPanel(id, title string, lines []string, footer string) Response {
259 return Response{Action: "open_panel", OpenPanel: &Panel{ID: id, Title: title, Lines: lines, Footer: footer}}259 return Response{Action: "open_panel", OpenPanel: &Panel{ID: id, Title: title, Lines: lines, Footer: footer}}
260 }260 }
@@ -269,7 +269,7 @@ func Errorf(format string, args ...any) Response {
269 return Response{Action: "noop", Error: fmt.Sprintf(format, args...)}269 return Response{Action: "noop", Error: fmt.Sprintf(format, args...)}
270 }270 }
271 271
272-// Extension is one zot extension. Construct with New, register272+// Extension is one zut extension. Construct with New, register
273 // commands, then call Run.273 // commands, then call Run.
274 type Extension struct {274 type Extension struct {
275 name string275 name string
@@ -315,11 +315,11 @@ type toolDef struct {
315 deferred bool315 deferred bool
316 }316 }
317 317
318-// HostInfo is what the host (zot) tells us in HelloAck. Useful for318+// HostInfo is what the host (zut) tells us in HelloAck. Useful for
319 // extensions that want to behave differently per provider.319 // extensions that want to behave differently per provider.
320 type HostInfo struct {320 type HostInfo struct {
321 ProtocolVersion int321 ProtocolVersion int
322- ZotVersion string322+ ZutVersion string
323 Provider string323 Provider string
324 Model string324 Model string
325 CWD string325 CWD string
@@ -350,9 +350,9 @@ func New(name, version string) *Extension {
350 // Returns the zero value if Run hasn't started yet.350 // Returns the zero value if Run hasn't started yet.
351 func (e *Extension) Host() HostInfo { return e.host }351 func (e *Extension) Host() HostInfo { return e.host }
352 352
353-// OnHello registers a callback that runs after zot acknowledges the353+// OnHello registers a callback that runs after zut acknowledges the
354 // hello frame and before commands/tools are announced. Use it when354 // hello frame and before commands/tools are announced. Use it when
355-// registrations depend on host metadata such as CWD or ZotHome-derived355+// registrations depend on host metadata such as CWD or ZutHome-derived
356 // paths. The callback may call Command, Tool, On, and interceptor356 // paths. The callback may call Command, Tool, On, and interceptor
357 // registration methods.357 // registration methods.
358 func (e *Extension) OnHello(fn func(HostInfo)) {358 func (e *Extension) OnHello(fn func(HostInfo)) {
@@ -361,8 +361,8 @@ func (e *Extension) OnHello(fn func(HostInfo)) {
361 e.mu.Unlock()361 e.mu.Unlock()
362 }362 }
363 363
364-// Logf writes a line to the extension's stderr, which zot captures to364+// Logf writes a line to the extension's stderr, which zut captures to
365-// $ZOT_HOME/logs/ext-<name>.log. Use this for debug output: anything365+// $ZUT_HOME/logs/ext-<name>.log. Use this for debug output: anything
366 // you print to stdout would corrupt the JSON wire protocol.366 // you print to stdout would corrupt the JSON wire protocol.
367 func (e *Extension) Logf(format string, args ...any) {367 func (e *Extension) Logf(format string, args ...any) {
368 fmt.Fprintf(e.stderr, "["+e.name+"] "+format+"\n", args...)368 fmt.Fprintf(e.stderr, "["+e.name+"] "+format+"\n", args...)
@@ -396,13 +396,13 @@ func (e *Extension) RenderPanel(panelID, title string, lines []string, footer st
396 _ = e.send(extproto.PanelRenderFromExt{Type: "panel_render", PanelID: panelID, Title: title, Lines: lines, Footer: footer})396 _ = e.send(extproto.PanelRenderFromExt{Type: "panel_render", PanelID: panelID, Title: title, Lines: lines, Footer: footer})
397 }397 }
398 398
399-// ClosePanel tells zot to close panelID.399+// ClosePanel tells zut to close panelID.
400 func (e *Extension) ClosePanel(panelID string) {400 func (e *Extension) ClosePanel(panelID string) {
401 _ = e.send(extproto.PanelCloseFromExt{Type: "panel_close", PanelID: panelID})401 _ = e.send(extproto.PanelCloseFromExt{Type: "panel_close", PanelID: panelID})
402 }402 }
403 403
404 // Command registers a slash-command handler. Call this BEFORE Run().404 // Command registers a slash-command handler. Call this BEFORE Run().
405-// Once Run is going, when the user runs /name in zot, fn is invoked405+// Once Run is going, when the user runs /name in zut, fn is invoked
406 // with the remaining args.406 // with the remaining args.
407 //407 //
408 // Naming conflicts with built-in commands (e.g. /help) are silently408 // Naming conflicts with built-in commands (e.g. /help) are silently
@@ -417,7 +417,7 @@ func (e *Extension) Command(name, description string, fn CommandHandler) {
417 417
418 // Tool registers an LLM-callable tool. schema is a JSON Schema418 // Tool registers an LLM-callable tool. schema is a JSON Schema
419 // object describing the tool's args (the same shape Anthropic /419 // object describing the tool's args (the same shape Anthropic /
420-// OpenAI accept). Call this BEFORE Run(); zot folds extension tools420+// OpenAI accept). Call this BEFORE Run(); zut folds extension tools
421 // into the agent's registry once the extension's ready frame fires.421 // into the agent's registry once the extension's ready frame fires.
422 //422 //
423 // Naming conflicts with built-in tools (read, write, edit, bash, lsp,423 // Naming conflicts with built-in tools (read, write, edit, bash, lsp,
@@ -501,7 +501,7 @@ func (e *Extension) InterceptAssistantMessage(fn AssistantMessageHandler) {
501 e.mu.Unlock()501 e.mu.Unlock()
502 }502 }
503 503
504-// Notify pushes an info-level status note into zot's chat without504+// Notify pushes an info-level status note into zut's chat without
505 // requiring a slash command from the user.505 // requiring a slash command from the user.
506 func (e *Extension) Notify(level, message string) {506 func (e *Extension) Notify(level, message string) {
507 _ = e.send(extproto.NotifyFromExt{507 _ = e.send(extproto.NotifyFromExt{
@@ -549,20 +549,21 @@ func (e *Extension) Alert(alert AlertRequest) {
549 })549 })
550 }550 }
551 551
552-// Run starts the protocol loop. Blocks until stdin closes (zot has552+// Run starts the protocol loop. Blocks until stdin closes (zut has
553 // shut us down). Returns the first fatal error, or nil on clean exit.553 // shut us down). Returns the first fatal error, or nil on clean exit.
554 func (e *Extension) Run() error {554 func (e *Extension) Run() error {
555 scanner := bufio.NewScanner(e.in)555 scanner := bufio.NewScanner(e.in)
556 scanner.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)556 scanner.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)
557 557
558- // Send hello first. zot answers with hello_ack immediately; read it558+ // Send hello first. zut answers with hello_ack immediately; read it
559 // synchronously so extensions can use HostInfo while registering559 // synchronously so extensions can use HostInfo while registering
560 // commands/tools before the ready frame.560 // commands/tools before the ready frame.
561 if err := e.send(extproto.HelloFromExt{561 if err := e.send(extproto.HelloFromExt{
562- Type: "hello",562+ Type: "hello",
563- Name: e.name,563+ ProtocolVersion: extproto.ProtocolVersion,
564- Version: e.version,564+ Name: e.name,
565- Capabilities: e.caps,565+ Version: e.version,
566+ Capabilities: e.caps,
566 }); err != nil {567 }); err != nil {
567 return err568 return err
568 }569 }
@@ -576,9 +577,12 @@ func (e *Extension) Run() error {
576 if ack.Type != "hello_ack" {577 if ack.Type != "hello_ack" {
577 return fmt.Errorf("first host frame must be hello_ack (got %q)", ack.Type)578 return fmt.Errorf("first host frame must be hello_ack (got %q)", ack.Type)
578 }579 }
580+ if ack.ProtocolVersion != extproto.ProtocolVersion {
581+ return fmt.Errorf("unsupported host protocol version %d (want %d)", ack.ProtocolVersion, extproto.ProtocolVersion)
582+ }
579 e.host = HostInfo{583 e.host = HostInfo{
580 ProtocolVersion: ack.ProtocolVersion,584 ProtocolVersion: ack.ProtocolVersion,
581- ZotVersion: ack.ZotVersion,585+ ZutVersion: ack.ZutVersion,
582 Provider: ack.Provider,586 Provider: ack.Provider,
583 Model: ack.Model,587 Model: ack.Model,
584 CWD: ack.CWD,588 CWD: ack.CWD,
M packages/agent/ext/ext_test.go +31 -3
@@ -9,7 +9,7 @@ import (
9 "testing"9 "testing"
10 "time"10 "time"
11 11
12- "github.com/patriceckhart/zot/packages/agent/extproto"12+ "github.com/bnema/zut/packages/agent/extproto"
13 )13 )
14 14
15 // ---------- test harness ----------15 // ---------- test harness ----------
@@ -140,10 +140,17 @@ func (h *extHarness) handshake(t *testing.T) {
140 if f.hdr.Type != "hello" {140 if f.hdr.Type != "hello" {
141 t.Fatalf("expected hello, got %q", f.hdr.Type)141 t.Fatalf("expected hello, got %q", f.hdr.Type)
142 }142 }
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+ }
143 h.sendToExt(t, extproto.HelloAckFromHost{150 h.sendToExt(t, extproto.HelloAckFromHost{
144 Type: "hello_ack",151 Type: "hello_ack",
145 ProtocolVersion: extproto.ProtocolVersion,152 ProtocolVersion: extproto.ProtocolVersion,
146- ZotVersion: "0.0.0-test",153+ ZutVersion: "0.0.0-test",
147 Provider: "anthropic",154 Provider: "anthropic",
148 Model: "claude-test",155 Model: "claude-test",
149 })156 })
@@ -157,6 +164,27 @@ func (h *extHarness) handshake(t *testing.T) {
157 164
158 // ---------- tests ----------165 // ---------- tests ----------
159 166
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+
160 // TestOnHelloCanRegisterUsingHostInfo checks that extensions can use188 // TestOnHelloCanRegisterUsingHostInfo checks that extensions can use
161 // host metadata before announcing their initial registrations.189 // host metadata before announcing their initial registrations.
162 func TestOnHelloCanRegisterUsingHostInfo(t *testing.T) {190 func TestOnHelloCanRegisterUsingHostInfo(t *testing.T) {
@@ -177,7 +205,7 @@ func TestOnHelloCanRegisterUsingHostInfo(t *testing.T) {
177 h.sendToExt(t, extproto.HelloAckFromHost{205 h.sendToExt(t, extproto.HelloAckFromHost{
178 Type: "hello_ack",206 Type: "hello_ack",
179 ProtocolVersion: extproto.ProtocolVersion,207 ProtocolVersion: extproto.ProtocolVersion,
180- ZotVersion: "0.0.0-test",208+ ZutVersion: "0.0.0-test",
181 Provider: "anthropic",209 Provider: "anthropic",
182 Model: "claude-test",210 Model: "claude-test",
183 CWD: "/tmp/project",211 CWD: "/tmp/project",
M packages/agent/extcmd.go +38 -38
@@ -14,12 +14,12 @@ import (
14 "strings"14 "strings"
15 "time"15 "time"
16 16
17- "github.com/patriceckhart/zot/packages/agent/extensions"17+ "github.com/bnema/zut/packages/agent/extensions"
18- "github.com/patriceckhart/zot/packages/agent/extproto"18+ "github.com/bnema/zut/packages/agent/extproto"
19- "github.com/patriceckhart/zot/packages/ignore"19+ "github.com/bnema/zut/packages/ignore"
20 )20 )
21 21
22-// runExtCommand dispatches `zot ext ...` subcommands. Returns22+// runExtCommand dispatches `zut ext ...` subcommands. Returns
23 // (handled=true, err) if rawArgs starts with "ext"; otherwise23 // (handled=true, err) if rawArgs starts with "ext"; otherwise
24 // (handled=false, nil) so the main router falls through to the24 // (handled=false, nil) so the main router falls through to the
25 // regular flag parser.25 // regular flag parser.
@@ -56,22 +56,22 @@ func runExtCommand(ctx context.Context, rawArgs []string, version string) (handl
56 }56 }
57 57
58 func printExtHelp() {58 func printExtHelp() {
59- fmt.Fprintln(os.Stderr, `zot ext — manage extensions59+ fmt.Fprintln(os.Stderr, `zut ext — manage extensions
60 60
61 usage:61 usage:
62- zot ext list list installed extensions and their state62+ zut ext list list installed extensions and their state
63- zot ext doctor diagnose installed extensions63+ zut ext doctor diagnose installed extensions
64- zot ext logs <name> [-f] cat / tail an extension's stderr log64+ zut ext logs <name> [-f] cat / tail an extension's stderr log
65- zot ext enable <name> re-enable a disabled extension65+ zut ext enable <name> re-enable a disabled extension
66- zot ext disable <name> disable without removing66+ zut ext disable <name> disable without removing
67- zot ext remove <name> delete an extension directory67+ zut ext remove <name> delete an extension directory
68- zot ext install [--build=go] <path|git-url>68+ zut ext install [--build=go] <path|git-url>
69 copy / clone and validate an extension69 copy / clone and validate an extension
70 --build=go explicitly builds a local Go extension70 --build=go explicitly builds a local Go extension
71 71
72 extensions live under:72 extensions live under:
73- $ZOT_HOME/extensions/<name>/extension.json (global)73+ $ZUT_HOME/extensions/<name>/extension.json (global)
74- ./.zot/extensions/<name>/extension.json (project-local)`)74+ ./.zut/extensions/<name>/extension.json (project-local)`)
75 }75 }
76 76
77 // extList walks both the global and project-local extension dirs and77 // extList walks both the global and project-local extension dirs and
@@ -122,7 +122,7 @@ func extList() error {
122 }122 }
123 if len(rows) == 0 {123 if len(rows) == 0 {
124 fmt.Fprintln(os.Stderr, "no extensions installed")124 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>`")
126 return nil126 return nil
127 }127 }
128 fmt.Printf("%-12s %-20s %-10s %-8s %-10s %s\n", "scope", "name", "version", "enabled", "language", "dir")128 fmt.Printf("%-12s %-20s %-10s %-8s %-10s %s\n", "scope", "name", "version", "enabled", "language", "dir")
@@ -169,14 +169,14 @@ func extDoctor(version string) error {
169 rows := scanExtDoctorStatic()169 rows := scanExtDoctorStatic()
170 if len(rows) == 0 {170 if len(rows) == 0 {
171 fmt.Fprintln(os.Stdout, "no extensions installed")171 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>`")
173 return nil173 return nil
174 }174 }
175 175
176 cwd, _ := os.Getwd()176 cwd, _ := os.Getwd()
177 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)177 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
178 defer cancel()178 defer cancel()
179- mgr := extensions.New(ZotHome(), cwd, version, "", "", extDoctorHooks{})179+ mgr := extensions.New(ZutHome(), cwd, version, "", "", extDoctorHooks{})
180 errs := mgr.Discover(ctx)180 errs := mgr.Discover(ctx)
181 mgr.WaitForReady(3 * time.Second)181 mgr.WaitForReady(3 * time.Second)
182 diags := mgr.Diagnostics()182 diags := mgr.Diagnostics()
@@ -187,7 +187,7 @@ func extDoctor(version string) error {
187 diagByDir[d.Dir] = d187 diagByDir[d.Dir] = d
188 }188 }
189 189
190- fmt.Fprintln(os.Stdout, "zot extension doctor")190+ fmt.Fprintln(os.Stdout, "zut extension doctor")
191 fmt.Fprintln(os.Stdout)191 fmt.Fprintln(os.Stdout)
192 for _, row := range rows {192 for _, row := range rows {
193 printExtDoctorRow(os.Stdout, row, diagByDir[row.Dir])193 printExtDoctorRow(os.Stdout, row, diagByDir[row.Dir])
@@ -208,9 +208,9 @@ func scanExtDoctorStatic() []extDoctorStaticRow {
208 }208 }
209 var dirs []scanDir209 var dirs []scanDir
210 if cwd, err := os.Getwd(); err == nil {210 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")})
212 }212 }
213- if h := ZotHome(); h != "" {213+ if h := ZutHome(); h != "" {
214 dirs = append(dirs, scanDir{scope: "global", dir: filepath.Join(h, "extensions")})214 dirs = append(dirs, scanDir{scope: "global", dir: filepath.Join(h, "extensions")})
215 }215 }
216 216
@@ -357,17 +357,17 @@ func printExtDoctorRow(w io.Writer, row extDoctorStaticRow, diag extensions.Exte
357 }357 }
358 358
359 func extDoctorLogPath(name string) string {359 func extDoctorLogPath(name string) string {
360- if name == "" || ZotHome() == "" {360+ if name == "" || ZutHome() == "" {
361 return ""361 return ""
362 }362 }
363- return filepath.Join(ZotHome(), "logs", "ext-"+name+".log")363+ return filepath.Join(ZutHome(), "logs", "ext-"+name+".log")
364 }364 }
365 365
366 // extLogs locates the named extension's log file and either cats or366 // extLogs locates the named extension's log file and either cats or
367 // tails it (-f).367 // tails it (-f).
368 func extLogs(args []string) error {368 func extLogs(args []string) error {
369 if len(args) == 0 {369 if len(args) == 0 {
370- return fmt.Errorf("usage: zot ext logs <name> [-f]")370+ return fmt.Errorf("usage: zut ext logs <name> [-f]")
371 }371 }
372 name := args[0]372 name := args[0]
373 follow := false373 follow := false
@@ -376,7 +376,7 @@ func extLogs(args []string) error {
376 follow = true376 follow = true
377 }377 }
378 }378 }
379- logPath := filepath.Join(ZotHome(), "logs", "ext-"+name+".log")379+ logPath := filepath.Join(ZutHome(), "logs", "ext-"+name+".log")
380 if _, err := os.Stat(logPath); err != nil {380 if _, err := os.Stat(logPath); err != nil {
381 return fmt.Errorf("no log for %q at %s", name, logPath)381 return fmt.Errorf("no log for %q at %s", name, logPath)
382 }382 }
@@ -402,7 +402,7 @@ func extToggle(args []string, enabled bool) error {
402 if !enabled {402 if !enabled {
403 verb = "disable"403 verb = "disable"
404 }404 }
405- return fmt.Errorf("usage: zot ext %s <name>", verb)405+ return fmt.Errorf("usage: zut ext %s <name>", verb)
406 }406 }
407 name := args[0]407 name := args[0]
408 dir, err := findExtensionDir(name)408 dir, err := findExtensionDir(name)
@@ -438,7 +438,7 @@ func extToggle(args []string, enabled bool) error {
438 // prompt (skip with --yes).438 // prompt (skip with --yes).
439 func extRemove(args []string) error {439 func extRemove(args []string) error {
440 if len(args) == 0 {440 if len(args) == 0 {
441- return fmt.Errorf("usage: zot ext remove <name> [--yes]")441+ return fmt.Errorf("usage: zut ext remove <name> [--yes]")
442 }442 }
443 name := args[0]443 name := args[0]
444 yes := false444 yes := false
@@ -468,7 +468,7 @@ func extRemove(args []string) error {
468 }468 }
469 469
470 // extInstall copies a local directory or shallow-clones a git URL470 // extInstall copies a local directory or shallow-clones a git URL
471-// into $ZOT_HOME/extensions/. It stages the install and validates the471+// into $ZUT_HOME/extensions/. It stages the install and validates the
472 // manifest plus any extension-local executable before reporting success.472 // manifest plus any extension-local executable before reporting success.
473 // Builds are never inferred or run implicitly; --build=go is an explicit473 // Builds are never inferred or run implicitly; --build=go is an explicit
474 // opt-in for local Go extensions.474 // opt-in for local Go extensions.
@@ -482,7 +482,7 @@ func extInstallContext(ctx context.Context, args []string) error {
482 return err482 return err
483 }483 }
484 src := opts.source484 src := opts.source
485- dest := filepath.Join(ZotHome(), "extensions")485+ dest := filepath.Join(ZutHome(), "extensions")
486 if err := os.MkdirAll(dest, 0o755); err != nil {486 if err := os.MkdirAll(dest, 0o755); err != nil {
487 return err487 return err
488 }488 }
@@ -517,7 +517,7 @@ func extInstallContext(ctx context.Context, args []string) error {
517 return fmt.Errorf("destination %s already exists; remove it first", out)517 return fmt.Errorf("destination %s already exists; remove it first", out)
518 }518 }
519 519
520- stageRoot, err := os.MkdirTemp(dest, ".zot-extension-install-")520+ stageRoot, err := os.MkdirTemp(dest, ".zut-extension-install-")
521 if err != nil {521 if err != nil {
522 return fmt.Errorf("create install staging directory: %w", err)522 return fmt.Errorf("create install staging directory: %w", err)
523 }523 }
@@ -548,7 +548,7 @@ type extInstallOptions struct {
548 548
549 func parseExtInstallArgs(args []string) (extInstallOptions, error) {549 func parseExtInstallArgs(args []string) (extInstallOptions, error) {
550 if len(args) == 0 {550 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>")
552 }552 }
553 553
554 var opts extInstallOptions554 var opts extInstallOptions
@@ -581,13 +581,13 @@ func parseExtInstallArgs(args []string) (extInstallOptions, error) {
581 case strings.HasPrefix(arg, "-"):581 case strings.HasPrefix(arg, "-"):
582 return extInstallOptions{}, fmt.Errorf("unknown ext install option %q", arg)582 return extInstallOptions{}, fmt.Errorf("unknown ext install option %q", arg)
583 case opts.source != "":583 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>")
585 default:585 default:
586 opts.source = arg586 opts.source = arg
587 }587 }
588 }588 }
589 if opts.source == "" {589 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>")
591 }591 }
592 if opts.builder != "" && opts.builder != "go" {592 if opts.builder != "" && opts.builder != "go" {
593 return extInstallOptions{}, fmt.Errorf("unsupported extension builder %q (currently supported: go)", opts.builder)593 return extInstallOptions{}, fmt.Errorf("unsupported extension builder %q (currently supported: go)", opts.builder)
@@ -639,7 +639,7 @@ func extensionRelativeExecPath(dir, execName string) (string, error) {
639 }639 }
640 640
641 func installGitExtension(ctx context.Context, src, dest string) error {641 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-")
643 if err != nil {643 if err != nil {
644 return fmt.Errorf("create install staging directory: %w", err)644 return fmt.Errorf("create install staging directory: %w", err)
645 }645 }
@@ -733,14 +733,14 @@ func validateExtensionExecutable(dir string, manifest extensions.Manifest) error
733 path, local := extensions.ResolveExecPath(dir, manifest.Exec)733 path, local := extensions.ResolveExecPath(dir, manifest.Exec)
734 if !local {734 if !local {
735 // Bare commands such as "go", "node", and "npx" are resolved735 // Bare commands such as "go", "node", and "npx" are resolved
736- // from PATH when zot starts. They may be intentionally installed736+ // from PATH when zut starts. They may be intentionally installed
737 // before the runtime is present on the current machine.737 // before the runtime is present on the current machine.
738 return nil738 return nil
739 }739 }
740 info, err := os.Stat(path)740 info, err := os.Stat(path)
741 if err != nil {741 if err != nil {
742 if os.IsNotExist(err) {742 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)
744 }744 }
745 return fmt.Errorf("stat extension executable %q: %w", manifest.Exec, err)745 return fmt.Errorf("stat extension executable %q: %w", manifest.Exec, err)
746 }746 }
@@ -755,11 +755,11 @@ func validateExtensionExecutable(dir string, manifest extensions.Manifest) error
755 755
756 func extensionDirs() map[string]string {756 func extensionDirs() map[string]string {
757 out := map[string]string{}757 out := map[string]string{}
758- if h := ZotHome(); h != "" {758+ if h := ZutHome(); h != "" {
759 out["global"] = filepath.Join(h, "extensions")759 out["global"] = filepath.Join(h, "extensions")
760 }760 }
761 if cwd, err := os.Getwd(); err == nil {761 if cwd, err := os.Getwd(); err == nil {
762- out["project"] = filepath.Join(cwd, ".zot", "extensions")762+ out["project"] = filepath.Join(cwd, ".zut", "extensions")
763 }763 }
764 return out764 return out
765 }765 }
@@ -782,7 +782,7 @@ func dashIfEmpty(s string) string {
782 }782 }
783 783
784 // copyDir does a recursive copy of src to dst preserving file mode784 // 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>`.
786 //786 //
787 // Entries matched by the source's root .gitignore are skipped, and787 // Entries matched by the source's root .gitignore are skipped, and
788 // .git itself is always skipped. This keeps non-portable, regeneratable788 // .git itself is always skipped. This keeps non-portable, regeneratable
M packages/agent/extcmd_test.go +21 -21
@@ -9,15 +9,15 @@ import (
9 "strings"9 "strings"
10 "testing"10 "testing"
11 11
12- "github.com/patriceckhart/zot/packages/agent/extensions"12+ "github.com/bnema/zut/packages/agent/extensions"
13 )13 )
14 14
15-// TestExtInstallDotSource verifies that `zot ext install .` resolves the15+// TestExtInstallDotSource verifies that `zut ext install .` resolves the
16 // source directory correctly instead of collapsing it to the extensions/16 // source directory correctly instead of collapsing it to the extensions/
17 // parent directory.17 // parent directory.
18 func TestExtInstallDotSource(t *testing.T) {18 func TestExtInstallDotSource(t *testing.T) {
19 home := t.TempDir()19 home := t.TempDir()
20- t.Setenv("ZOT_HOME", home)20+ t.Setenv("ZUT_HOME", home)
21 21
22 // Pre-create extensions/ to mimic a normal first run.22 // Pre-create extensions/ to mimic a normal first run.
23 if err := os.MkdirAll(filepath.Join(home, "extensions"), 0o755); err != nil {23 if err := os.MkdirAll(filepath.Join(home, "extensions"), 0o755); err != nil {
@@ -56,7 +56,7 @@ func TestExtInstallDotSource(t *testing.T) {
56 // successfully when its manifest uses a PATH-resolved executable.56 // successfully when its manifest uses a PATH-resolved executable.
57 func TestExtInstallNamedDir(t *testing.T) {57 func TestExtInstallNamedDir(t *testing.T) {
58 home := t.TempDir()58 home := t.TempDir()
59- t.Setenv("ZOT_HOME", home)59+ t.Setenv("ZUT_HOME", home)
60 60
61 src := filepath.Join(t.TempDir(), "myext")61 src := filepath.Join(t.TempDir(), "myext")
62 if err := os.MkdirAll(src, 0o755); err != nil {62 if err := os.MkdirAll(src, 0o755); err != nil {
@@ -76,7 +76,7 @@ func TestExtInstallNamedDir(t *testing.T) {
76 76
77 func TestExtInstallThemeOnly(t *testing.T) {77 func TestExtInstallThemeOnly(t *testing.T) {
78 home := t.TempDir()78 home := t.TempDir()
79- t.Setenv("ZOT_HOME", home)79+ t.Setenv("ZUT_HOME", home)
80 80
81 src := filepath.Join(t.TempDir(), "theme-source")81 src := filepath.Join(t.TempDir(), "theme-source")
82 if err := os.MkdirAll(src, 0o755); err != nil {82 if err := os.MkdirAll(src, 0o755); err != nil {
@@ -102,7 +102,7 @@ func TestExtInstallThemeOnly(t *testing.T) {
102 102
103 func TestExtInstallRejectsMissingExecutable(t *testing.T) {103 func TestExtInstallRejectsMissingExecutable(t *testing.T) {
104 home := t.TempDir()104 home := t.TempDir()
105- t.Setenv("ZOT_HOME", home)105+ t.Setenv("ZUT_HOME", home)
106 106
107 src := filepath.Join(t.TempDir(), "tasked-phases")107 src := filepath.Join(t.TempDir(), "tasked-phases")
108 if err := os.MkdirAll(src, 0o755); err != nil {108 if err := os.MkdirAll(src, 0o755); err != nil {
@@ -127,7 +127,7 @@ func TestExtInstallBuildGo(t *testing.T) {
127 }127 }
128 128
129 home := t.TempDir()129 home := t.TempDir()
130- t.Setenv("ZOT_HOME", home)130+ t.Setenv("ZUT_HOME", home)
131 src := filepath.Join(t.TempDir(), "go-extension")131 src := filepath.Join(t.TempDir(), "go-extension")
132 if err := os.MkdirAll(src, 0o755); err != nil {132 if err := os.MkdirAll(src, 0o755); err != nil {
133 t.Fatal(err)133 t.Fatal(err)
@@ -165,7 +165,7 @@ func TestExtInstallBuildFailureLeavesNoDestination(t *testing.T) {
165 }165 }
166 166
167 home := t.TempDir()167 home := t.TempDir()
168- t.Setenv("ZOT_HOME", home)168+ t.Setenv("ZUT_HOME", home)
169 src := filepath.Join(t.TempDir(), "broken-go")169 src := filepath.Join(t.TempDir(), "broken-go")
170 if err := os.MkdirAll(src, 0o755); err != nil {170 if err := os.MkdirAll(src, 0o755); err != nil {
171 t.Fatal(err)171 t.Fatal(err)
@@ -192,7 +192,7 @@ func TestExtInstallBuildFailureLeavesNoDestination(t *testing.T) {
192 t.Fatal(err)192 t.Fatal(err)
193 }193 }
194 for _, entry := range entries {194 for _, entry := range entries {
195- if strings.HasPrefix(entry.Name(), ".zot-extension-install-") {195+ if strings.HasPrefix(entry.Name(), ".zut-extension-install-") {
196 t.Fatalf("failed build left staging directory %s", entry.Name())196 t.Fatalf("failed build left staging directory %s", entry.Name())
197 }197 }
198 }198 }
@@ -240,7 +240,7 @@ func TestParseExtInstallArgs(t *testing.T) {
240 240
241 func TestExtInstallRejectsBuildForThemeOnly(t *testing.T) {241 func TestExtInstallRejectsBuildForThemeOnly(t *testing.T) {
242 home := t.TempDir()242 home := t.TempDir()
243- t.Setenv("ZOT_HOME", home)243+ t.Setenv("ZUT_HOME", home)
244 src := filepath.Join(t.TempDir(), "theme-source")244 src := filepath.Join(t.TempDir(), "theme-source")
245 if err := os.MkdirAll(src, 0o755); err != nil {245 if err := os.MkdirAll(src, 0o755); err != nil {
246 t.Fatal(err)246 t.Fatal(err)
@@ -263,7 +263,7 @@ func TestExtInstallRejectsBuildForThemeOnly(t *testing.T) {
263 263
264 func TestExtInstallRejectsWhitespaceManifestName(t *testing.T) {264 func TestExtInstallRejectsWhitespaceManifestName(t *testing.T) {
265 home := t.TempDir()265 home := t.TempDir()
266- t.Setenv("ZOT_HOME", home)266+ t.Setenv("ZUT_HOME", home)
267 src := filepath.Join(t.TempDir(), "whitespace-name")267 src := filepath.Join(t.TempDir(), "whitespace-name")
268 if err := os.MkdirAll(src, 0o755); err != nil {268 if err := os.MkdirAll(src, 0o755); err != nil {
269 t.Fatal(err)269 t.Fatal(err)
@@ -280,7 +280,7 @@ func TestExtInstallRejectsWhitespaceManifestName(t *testing.T) {
280 280
281 func TestExtInstallRejectsDotManifestName(t *testing.T) {281 func TestExtInstallRejectsDotManifestName(t *testing.T) {
282 home := t.TempDir()282 home := t.TempDir()
283- t.Setenv("ZOT_HOME", home)283+ t.Setenv("ZUT_HOME", home)
284 src := filepath.Join(t.TempDir(), "dot-name")284 src := filepath.Join(t.TempDir(), "dot-name")
285 if err := os.MkdirAll(src, 0o755); err != nil {285 if err := os.MkdirAll(src, 0o755); err != nil {
286 t.Fatal(err)286 t.Fatal(err)
@@ -297,7 +297,7 @@ func TestExtInstallRejectsDotManifestName(t *testing.T) {
297 297
298 func TestExtInstallRejectsBuildForPATHExecutable(t *testing.T) {298 func TestExtInstallRejectsBuildForPATHExecutable(t *testing.T) {
299 home := t.TempDir()299 home := t.TempDir()
300- t.Setenv("ZOT_HOME", home)300+ t.Setenv("ZUT_HOME", home)
301 src := filepath.Join(t.TempDir(), "path-extension")301 src := filepath.Join(t.TempDir(), "path-extension")
302 if err := os.MkdirAll(src, 0o755); err != nil {302 if err := os.MkdirAll(src, 0o755); err != nil {
303 t.Fatal(err)303 t.Fatal(err)
@@ -321,7 +321,7 @@ func TestExtInstallRejectsNonExecutable(t *testing.T) {
321 }321 }
322 322
323 home := t.TempDir()323 home := t.TempDir()
324- t.Setenv("ZOT_HOME", home)324+ t.Setenv("ZUT_HOME", home)
325 src := filepath.Join(t.TempDir(), "non-executable")325 src := filepath.Join(t.TempDir(), "non-executable")
326 if err := os.MkdirAll(src, 0o755); err != nil {326 if err := os.MkdirAll(src, 0o755); err != nil {
327 t.Fatal(err)327 t.Fatal(err)
@@ -347,7 +347,7 @@ func TestExtInstallRejectsNonExecutable(t *testing.T) {
347 // the manifest name controls the installed directory.347 // the manifest name controls the installed directory.
348 func TestExtInstallCopiesIgnoredExecutableAndUsesManifestName(t *testing.T) {348 func TestExtInstallCopiesIgnoredExecutableAndUsesManifestName(t *testing.T) {
349 home := t.TempDir()349 home := t.TempDir()
350- t.Setenv("ZOT_HOME", home)350+ t.Setenv("ZUT_HOME", home)
351 351
352 src := filepath.Join(t.TempDir(), "source-dir")352 src := filepath.Join(t.TempDir(), "source-dir")
353 if err := os.MkdirAll(src, 0o755); err != nil {353 if err := os.MkdirAll(src, 0o755); err != nil {
@@ -434,7 +434,7 @@ func TestGitignoreNegation(t *testing.T) {
434 434
435 func TestExtDoctorStaticScanAndRender(t *testing.T) {435 func TestExtDoctorStaticScanAndRender(t *testing.T) {
436 home := t.TempDir()436 home := t.TempDir()
437- t.Setenv("ZOT_HOME", home)437+ t.Setenv("ZUT_HOME", home)
438 cwd := t.TempDir()438 cwd := t.TempDir()
439 oldCWD, err := os.Getwd()439 oldCWD, err := os.Getwd()
440 if err != nil {440 if err != nil {
@@ -455,11 +455,11 @@ func TestExtDoctorStaticScanAndRender(t *testing.T) {
455 }455 }
456 }456 }
457 457
458- mustWrite(filepath.Join(cwd, ".zot", "extensions", "disabled", "extension.json"), `{"name":"disabled","exec":"./run.sh","enabled":false}`)458+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "disabled", "extension.json"), `{"name":"disabled","exec":"./run.sh","enabled":false}`)
459- mustWrite(filepath.Join(cwd, ".zot", "extensions", "theme", "extension.json"), `{"name":"theme"}`)459+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "theme", "extension.json"), `{"name":"theme"}`)
460- mustWrite(filepath.Join(cwd, ".zot", "extensions", "theme", "theme.json"), `{"name":"Theme"}`)460+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "theme", "theme.json"), `{"name":"Theme"}`)
461- mustWrite(filepath.Join(cwd, ".zot", "extensions", "bad", "extension.json"), `{bad json`)461+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "bad", "extension.json"), `{bad json`)
462- mustWrite(filepath.Join(cwd, ".zot", "extensions", "dup", "extension.json"), `{"name":"dup","exec":"./project.sh"}`)462+ mustWrite(filepath.Join(cwd, ".zut", "extensions", "dup", "extension.json"), `{"name":"dup","exec":"./project.sh"}`)
463 mustWrite(filepath.Join(home, "extensions", "dup", "extension.json"), `{"name":"dup","exec":"./global.sh"}`)463 mustWrite(filepath.Join(home, "extensions", "dup", "extension.json"), `{"name":"dup","exec":"./global.sh"}`)
464 464
465 rows := scanExtDoctorStatic()465 rows := scanExtDoctorStatic()
M packages/agent/extensions/events.go +3 -3
@@ -8,7 +8,7 @@ import (
8 "time"8 "time"
9 "unicode/utf8"9 "unicode/utf8"
10 10
11- "github.com/patriceckhart/zot/packages/agent/extproto"11+ "github.com/bnema/zut/packages/agent/extproto"
12 )12 )
13 13
14 // EmitEvent fires a one-way lifecycle event to every extension that14 // 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
304 ext.mu.Lock()304 ext.mu.Lock()
305 delete(ext.pendingIntercept, id)305 delete(ext.pendingIntercept, id)
306 ext.mu.Unlock()306 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)
308 return InterceptResult{}308 return InterceptResult{}
309 }309 }
310 310
@@ -321,7 +321,7 @@ func (m *Manager) askIntercept(ctx context.Context, ext *Extension, payload extp
321 ext.mu.Lock()321 ext.mu.Lock()
322 delete(ext.pendingIntercept, id)322 delete(ext.pendingIntercept, id)
323 ext.mu.Unlock()323 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)
325 return InterceptResult{}325 return InterceptResult{}
326 case <-ctx.Done():326 case <-ctx.Done():
327 ext.mu.Lock()327 ext.mu.Lock()
M packages/agent/extensions/intercept_test.go +3 -3
@@ -39,7 +39,7 @@ func TestInterceptAllThreeEvents(t *testing.T) {
39 extDir := t.TempDir()39 extDir := t.TempDir()
40 script := `#!/bin/bash40 script := `#!/bin/bash
41 emit() { printf '%s\n' "$1"; }41 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"]}'
43 emit '{"type":"subscribe","events":[],"intercept":["tool_call","turn_start","assistant_message"]}'43 emit '{"type":"subscribe","events":[],"intercept":["tool_call","turn_start","assistant_message"]}'
44 emit '{"type":"ready"}'44 emit '{"type":"ready"}'
45 while IFS= read -r line; do45 while IFS= read -r line; do
@@ -83,7 +83,7 @@ done
83 t.Fatal(err)83 t.Fatal(err)
84 }84 }
85 85
86- // ZotHome is unused here; we load the extension explicitly.86+ // ZutHome is unused here; we load the extension explicitly.
87 m := New(t.TempDir(), "", "0.0.0-test", "anthropic", "claude-test", nil)87 m := New(t.TempDir(), "", "0.0.0-test", "anthropic", "claude-test", nil)
88 t.Cleanup(func() { m.Stop(2 * time.Second) })88 t.Cleanup(func() { m.Stop(2 * time.Second) })
89 89
@@ -159,7 +159,7 @@ func TestReloadRespawnsExtensions(t *testing.T) {
159 extDir := t.TempDir()159 extDir := t.TempDir()
160 script := `#!/bin/bash160 script := `#!/bin/bash
161 emit() { printf '%s\n' "$1"; }161 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"]}'
163 emit '{"type":"register_command","name":"rtest","description":"test"}'163 emit '{"type":"register_command","name":"rtest","description":"test"}'
164 emit '{"type":"ready"}'164 emit '{"type":"ready"}'
165 while IFS= read -r line; do165 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 subprocess1+// Package extensions implements the host side of zut's subprocess
2 // extension protocol. The Manager discovers extensions in well-known2 // extension protocol. The Manager discovers extensions in well-known
3 // directories, spawns each one, completes the hello handshake, and3 // directories, spawns each one, completes the hello handshake, and
4 // routes slash commands to the right extension.4 // routes slash commands to the right extension.
5 //5 //
6-// Each extension is its own process, communicating with zot over its6+// Each extension is its own process, communicating with zut over its
7 // stdin/stdout in newline-delimited JSON. Stderr is redirected to a7 // stdin/stdout in newline-delimited JSON. Stderr is redirected to a
8-// per-extension log file under $ZOT_HOME/logs/. Crashing one8+// per-extension log file under $ZUT_HOME/logs/. Crashing one
9 // extension does not affect the others or the host.9 // extension does not affect the others or the host.
10 //10 //
11 // See docs/extensions.md for the user-facing reference and11 // See docs/extensions.md for the user-facing reference and
@@ -28,13 +28,13 @@ import (
28 "syscall"28 "syscall"
29 "time"29 "time"
30 30
31- "github.com/patriceckhart/zot/packages/agent/extproto"31+ "github.com/bnema/zut/packages/agent/extproto"
32- "github.com/patriceckhart/zot/packages/agent/skills"32+ "github.com/bnema/zut/packages/agent/skills"
33- "github.com/patriceckhart/zot/packages/tui"33+ "github.com/bnema/zut/packages/tui"
34 )34 )
35 35
36 // Manifest is the extension.json file shipped alongside an36 // Manifest is the extension.json file shipped alongside an
37-// extension's executable. It tells zot how to launch the extension37+// extension's executable. It tells zut how to launch the extension
38 // and provides display metadata.38 // and provides display metadata.
39 type Manifest struct {39 type Manifest struct {
40 Name string `json:"name"`40 Name string `json:"name"`
@@ -49,7 +49,7 @@ type Manifest struct {
49 49
50 // IsEnabled returns the manifest's effective enabled state. Default50 // IsEnabled returns the manifest's effective enabled state. Default
51 // is true so adding a new extension folder Just Works without an51 // is true so adding a new extension folder Just Works without an
52-// extra zot ext enable command.52+// extra zut ext enable command.
53 func (m Manifest) IsEnabled() bool {53 func (m Manifest) IsEnabled() bool {
54 return m.Enabled == nil || *m.Enabled54 return m.Enabled == nil || *m.Enabled
55 }55 }
@@ -96,7 +96,7 @@ func loadManifestSkills(extDir string, mf Manifest) ([]*skills.Skill, []error) {
96 return loaded, append(errs, loadErrs...)96 return loaded, append(errs, loadErrs...)
97 }97 }
98 98
99-// Extension is a running extension subprocess and the metadata zot99+// Extension is a running extension subprocess and the metadata zut
100 // tracks about it.100 // tracks about it.
101 type Extension struct {101 type Extension struct {
102 Manifest Manifest102 Manifest Manifest
@@ -211,11 +211,11 @@ type commandRegistration struct {
211 name string211 name string
212 }212 }
213 213
214-// Manager owns every extension subprocess for the lifetime of zot.214+// Manager owns every extension subprocess for the lifetime of zut.
215 type Manager struct {215 type Manager struct {
216- zotHome string216+ zutHome string
217 cwd string217 cwd string
218- zotVersion string218+ zutVersion string
219 provider string219 provider string
220 model string220 model string
221 hooks HostHooks221 hooks HostHooks
@@ -254,11 +254,11 @@ type sessionEventState struct {
254 254
255 // New constructs an empty Manager. Call Discover to populate it from255 // New constructs an empty Manager. Call Discover to populate it from
256 // the on-disk extension directories.256 // 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 {
258 return &Manager{258 return &Manager{
259- zotHome: zotHome,259+ zutHome: zutHome,
260 cwd: cwd,260 cwd: cwd,
261- zotVersion: zotVersion,261+ zutVersion: zutVersion,
262 provider: provider,262 provider: provider,
263 model: model,263 model: model,
264 hooks: hooks,264 hooks: hooks,
@@ -324,10 +324,10 @@ func (m *Manager) Discover(ctx context.Context) []error {
324 func (m *Manager) searchDirs() []string {324 func (m *Manager) searchDirs() []string {
325 var dirs []string325 var dirs []string
326 if m.cwd != "" {326 if m.cwd != "" {
327- dirs = append(dirs, filepath.Join(m.cwd, ".zot", "extensions"))327+ dirs = append(dirs, filepath.Join(m.cwd, ".zut", "extensions"))
328 }328 }
329- if m.zotHome != "" {329+ if m.zutHome != "" {
330- dirs = append(dirs, filepath.Join(m.zotHome, "extensions"))330+ dirs = append(dirs, filepath.Join(m.zutHome, "extensions"))
331 }331 }
332 return dirs332 return dirs
333 }333 }
@@ -352,7 +352,7 @@ func (m *Manager) loadOne(ctx context.Context, dir string) error {
352 return errors.New("manifest: exec is required")352 return errors.New("manifest: exec is required")
353 }353 }
354 if !mf.IsEnabled() {354 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.
356 return nil356 return nil
357 }357 }
358 358
@@ -400,8 +400,8 @@ func (m *Manager) loadOne(ctx context.Context, dir string) error {
400 }400 }
401 401
402 // LoadExplicit loads each path as an ad-hoc extension. Used for402 // LoadExplicit loads each path as an ad-hoc extension. Used for
403-// `zot --ext <path>` so extension authors can iterate on a working403+// `zut --ext <path>` so extension authors can iterate on a working
404-// copy without having to `zot ext install` after every change.404+// copy without having to `zut ext install` after every change.
405 //405 //
406 // Loaded BEFORE Discover so explicit paths win on name conflicts406 // Loaded BEFORE Discover so explicit paths win on name conflicts
407 // against installed extensions. Spawns happen in parallel like the407 // against installed extensions. Spawns happen in parallel like the
@@ -540,7 +540,7 @@ func (m *Manager) Reload(ctx context.Context, grace time.Duration) ReloadStats {
540 stats.Loaded = len(m.ext)540 stats.Loaded = len(m.ext)
541 m.mu.RUnlock()541 m.mu.RUnlock()
542 542
543- // Wait for ready frames. Use the same 3s grace zot uses at543+ // Wait for ready frames. Use the same 3s grace zut uses at
544 // startup so the reload feels no slower than a cold boot.544 // startup so the reload feels no slower than a cold boot.
545 readyDeadline := time.Now().Add(grace)545 readyDeadline := time.Now().Add(grace)
546 if time.Until(readyDeadline) < 3*time.Second {546 if time.Until(readyDeadline) < 3*time.Second {
@@ -570,7 +570,7 @@ func (m *Manager) Reload(ctx context.Context, grace time.Duration) ReloadStats {
570 //570 //
571 // Waits run in parallel: total time is max(per-extension wait), not571 // Waits run in parallel: total time is max(per-extension wait), not
572 // sum. Without this, a single slow extension (e.g. `npx tsx` cold)572 // sum. Without this, a single slow extension (e.g. `npx tsx` cold)
573-// would gate every other extension's wait too and zot startup would573+// would gate every other extension's wait too and zut startup would
574 // scale linearly with the number of slow runtimes installed.574 // scale linearly with the number of slow runtimes installed.
575 //575 //
576 // Call after Discover and before relying on tool registrations.576 // Call after Discover and before relying on tool registrations.
@@ -595,7 +595,7 @@ func (m *Manager) WaitForReady(grace time.Duration) {
595 ext.readyTimedOut = true595 ext.readyTimedOut = true
596 ext.diagnostics = append(ext.diagnostics, "timed out waiting for ready frame")596 ext.diagnostics = append(ext.diagnostics, "timed out waiting for ready frame")
597 ext.mu.Unlock()597 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")
599 ext.readyOnce.Do(func() { close(ext.readyCh) })599 ext.readyOnce.Do(func() { close(ext.readyCh) })
600 }600 }
601 }(ext)601 }(ext)
@@ -628,7 +628,7 @@ func ResolveExecPath(extDir, execName string) (string, bool) {
628 // runs the synchronous portion of the hello handshake. Asynchronous628 // runs the synchronous portion of the hello handshake. Asynchronous
629 // frames are processed in a goroutine started here.629 // frames are processed in a goroutine started here.
630 func (m *Manager) spawn(ctx context.Context, ext *Extension) error {630 func (m *Manager) spawn(ctx context.Context, ext *Extension) error {
631- logsDir := filepath.Join(m.zotHome, "logs")631+ logsDir := filepath.Join(m.zutHome, "logs")
632 _ = os.MkdirAll(logsDir, 0o755)632 _ = os.MkdirAll(logsDir, 0o755)
633 logPath := filepath.Join(logsDir, "ext-"+ext.Manifest.Name+".log")633 logPath := filepath.Join(logsDir, "ext-"+ext.Manifest.Name+".log")
634 logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)634 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 {
637 }637 }
638 ext.LogPath = logPath638 ext.LogPath = logPath
639 ext.logFile = logFile639 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))
641 641
642 // Bare names (node, npx, python3, tsx, etc.) are looked up via PATH;642 // Bare names (node, npx, python3, tsx, etc.) are looked up via PATH;
643 // absolute and extension-relative paths are resolved by the helper.643 // absolute and extension-relative paths are resolved by the helper.
@@ -707,6 +707,9 @@ func (m *Manager) spawn(ctx context.Context, ext *Extension) error {
707 if hello.Type != "hello" || hello.Name == "" {707 if hello.Type != "hello" || hello.Name == "" {
708 return fmt.Errorf("first frame must be hello (got %q; stderr log: %s)", hello.Type, logPath)708 return fmt.Errorf("first frame must be hello (got %q; stderr log: %s)", hello.Type, logPath)
709 }709 }
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+ }
710 // Trust the manifest's name; ignore mismatch from the hello.713 // Trust the manifest's name; ignore mismatch from the hello.
711 ext.helloAck = true714 ext.helloAck = true
712 715
@@ -719,7 +722,7 @@ func (m *Manager) spawn(ctx context.Context, ext *Extension) error {
719 ack, _ := extproto.Encode(extproto.HelloAckFromHost{722 ack, _ := extproto.Encode(extproto.HelloAckFromHost{
720 Type: "hello_ack",723 Type: "hello_ack",
721 ProtocolVersion: extproto.ProtocolVersion,724 ProtocolVersion: extproto.ProtocolVersion,
722- ZotVersion: m.zotVersion,725+ ZutVersion: m.zutVersion,
723 Provider: m.provider,726 Provider: m.provider,
724 Model: m.model,727 Model: m.model,
725 CWD: m.cwd,728 CWD: m.cwd,
@@ -800,7 +803,7 @@ func (m *Manager) assumeReadyAfterIdle(ext *Extension) {
800 ext.autoReady = true803 ext.autoReady = true
801 ext.diagnostics = append(ext.diagnostics, "no ready frame; auto-ready after idle")804 ext.diagnostics = append(ext.diagnostics, "no ready frame; auto-ready after idle")
802 ext.mu.Unlock()805 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")
804 close(ext.readyCh)807 close(ext.readyCh)
805 })808 })
806 return809 return
@@ -833,7 +836,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
833 }836 }
834 ext.stopLifecycleWriter()837 ext.stopLifecycleWriter()
835 ext.readyOnce.Do(func() { close(ext.readyCh) })838 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))
837 }()840 }()
838 841
839 for scanner.Scan() {842 for scanner.Scan() {
@@ -844,7 +847,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
844 var frame extproto.Frame847 var frame extproto.Frame
845 if err := json.Unmarshal(line, &frame); err != nil {848 if err := json.Unmarshal(line, &frame); err != nil {
846 ext.recordDiagnostic(fmt.Sprintf("malformed json frame: %v", err))849 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)
848 continue851 continue
849 }852 }
850 switch frame.Type {853 switch frame.Type {
@@ -870,7 +873,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
870 case "register_tool":873 case "register_tool":
871 var rt extproto.RegisterToolFromExt874 var rt extproto.RegisterToolFromExt
872 if err := json.Unmarshal(line, &rt); err != nil {875 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)
874 continue877 continue
875 }878 }
876 // Validate the schema parses as JSON. If not, refuse to879 // Validate the schema parses as JSON. If not, refuse to
@@ -879,7 +882,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
879 var tmp any882 var tmp any
880 if err := json.Unmarshal(rt.Schema, &tmp); err != nil {883 if err := json.Unmarshal(rt.Schema, &tmp); err != nil {
881 ext.recordDiagnostic(fmt.Sprintf("tool %q schema is not valid json: %v", rt.Name, err))884 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)
883 continue886 continue
884 }887 }
885 }888 }
@@ -993,7 +996,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
993 if strings.HasPrefix(text, "/") && m.hooks != nil {996 if strings.HasPrefix(text, "/") && m.hooks != nil {
994 m.hooks.SubmitSlash(text)997 m.hooks.SubmitSlash(text)
995 } else {998 } 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)
997 }1000 }
998 }1001 }
999 case "command_response":1002 case "command_response":
@@ -1031,7 +1034,7 @@ func (m *Manager) readLoop(ext *Extension, scanner *bufio.Scanner) {
1031 // Caller of Stop is waiting on the process exit, not this frame.1034 // Caller of Stop is waiting on the process exit, not this frame.
1032 default:1035 default:
1033 ext.recordDiagnostic(fmt.Sprintf("unknown frame type %q", frame.Type))1036 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)
1035 }1038 }
1036 }1039 }
1037 }1040 }
@@ -1177,7 +1180,7 @@ type ExtensionDiagnostic struct {
1177 }1180 }
1178 1181
1179 // Diagnostics returns a snapshot of loaded extension state for commands such1182 // 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.
1181 func (m *Manager) Diagnostics() []ExtensionDiagnostic {1184 func (m *Manager) Diagnostics() []ExtensionDiagnostic {
1182 m.mu.RLock()1185 m.mu.RLock()
1183 defer m.mu.RUnlock()1186 defer m.mu.RUnlock()
@@ -1253,7 +1256,7 @@ func (m *Manager) Commands() []CommandInfo {
1253 }1256 }
1254 1257
1255 // CommandInfo is one extension-registered slash command, surfaced to1258 // 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.
1257 type CommandInfo struct {1260 type CommandInfo struct {
1258 Extension string1261 Extension string
1259 Name string1262 Name string
@@ -1509,7 +1512,7 @@ func stopExtensions(exts []*Extension, gracePeriod time.Duration) {
1509 }1512 }
1510 1513
1511 // All returns every extension currently tracked, enabled or not.1514 // All returns every extension currently tracked, enabled or not.
1512-// Used by `zot ext list`.1515+// Used by `zut ext list`.
1513 func (m *Manager) All() []*Extension {1516 func (m *Manager) All() []*Extension {
1514 m.mu.RLock()1517 m.mu.RLock()
1515 defer m.mu.RUnlock()1518 defer m.mu.RUnlock()
M packages/agent/extensions/manager_test.go +36 -7
@@ -6,7 +6,7 @@ import (
6 "io"6 "io"
7 "os"7 "os"
8 8
9- "github.com/patriceckhart/zot/packages/agent/extproto"9+ "github.com/bnema/zut/packages/agent/extproto"
10 "path/filepath"10 "path/filepath"
11 "runtime"11 "runtime"
12 "strings"12 "strings"
@@ -123,7 +123,7 @@ func writeMockExtension(t *testing.T, root string) {
123 // stdin closes; tail's -F keeps the pipe alive long enough for123 // stdin closes; tail's -F keeps the pipe alive long enough for
124 // the manager to send command_invoked.124 // the manager to send command_invoked.
125 script := `#!/bin/sh125 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"]}'
127 printf '%s\n' '{"type":"register_command","name":"Ping","description":"ping/pong"}'127 printf '%s\n' '{"type":"register_command","name":"Ping","description":"ping/pong"}'
128 while IFS= read -r line; do128 while IFS= read -r line; do
129 case "$line" in129 case "$line" in
@@ -260,6 +260,35 @@ func TestSpawnCleansUpProcessAfterInvalidHello(t *testing.T) {
260 }260 }
261 }261 }
262 262
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+
263 func TestDiscoverLoadsThemeOnlyExtension(t *testing.T) {292 func TestDiscoverLoadsThemeOnlyExtension(t *testing.T) {
264 tmp := t.TempDir()293 tmp := t.TempDir()
265 extDir := filepath.Join(tmp, "extensions", "theme-only")294 extDir := filepath.Join(tmp, "extensions", "theme-only")
@@ -423,7 +452,7 @@ func TestDiagnosticsReportMalformedFramesAndConflicts(t *testing.T) {
423 }452 }
424 453
425 writeDiagExtension("a-first", `#!/bin/sh454 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"]}'
427 printf '%s\n' '{"type":"register_command","name":"CaseTest","description":"first"}'456 printf '%s\n' '{"type":"register_command","name":"CaseTest","description":"first"}'
428 printf '%s\n' '{"type":"register_tool","name":"shared","description":"first","schema":{"type":"object"}}'457 printf '%s\n' '{"type":"register_tool","name":"shared","description":"first","schema":{"type":"object"}}'
429 printf '%s\n' '{"type":"register_tool","name":"broken","description":"bad","schema":'458 printf '%s\n' '{"type":"register_tool","name":"broken","description":"bad","schema":'
@@ -435,7 +464,7 @@ while IFS= read -r line; do
435 done464 done
436 `)465 `)
437 writeDiagExtension("b-second", `#!/bin/sh466 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"]}'
439 printf '%s\n' '{"type":"register_command","name":"casetest","description":"second"}'468 printf '%s\n' '{"type":"register_command","name":"casetest","description":"second"}'
440 printf '%s\n' '{"type":"register_tool","name":"shared","description":"second","schema":{"type":"object"}}'469 printf '%s\n' '{"type":"register_tool","name":"shared","description":"second","schema":{"type":"object"}}'
441 printf '%s\n' '{"type":"ready"}'470 printf '%s\n' '{"type":"ready"}'
@@ -512,7 +541,7 @@ func TestSpontaneousSubmit(t *testing.T) {
512 }541 }
513 542
514 script := `#!/bin/sh543 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"]}'
516 printf '%s\n' '{"type":"ready"}'545 printf '%s\n' '{"type":"ready"}'
517 printf '%s\n' '{"type":"submit","text":" explain this repository briefly "}'546 printf '%s\n' '{"type":"submit","text":" explain this repository briefly "}'
518 printf '%s\n' '{"type":"submit","text":" "}'547 printf '%s\n' '{"type":"submit","text":" "}'
@@ -575,7 +604,7 @@ func TestSpontaneousAlert(t *testing.T) {
575 }604 }
576 605
577 script := `#!/bin/sh606 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"]}'
579 printf '%s\n' '{"type":"ready"}'608 printf '%s\n' '{"type":"ready"}'
580 printf '%s\n' '{"type":"alert","kind":"bell","reason":"question_ready"}'609 printf '%s\n' '{"type":"alert","kind":"bell","reason":"question_ready"}'
581 while IFS= read -r line; do610 while IFS= read -r line; do
@@ -643,7 +672,7 @@ func TestSpontaneousOpenPanel(t *testing.T) {
643 // Extension emits hello + ready, then immediately fires a672 // Extension emits hello + ready, then immediately fires a
644 // spontaneous open_panel, then waits for shutdown.673 // spontaneous open_panel, then waits for shutdown.
645 script := `#!/bin/sh674 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"]}'
647 printf '%s\n' '{"type":"ready"}'676 printf '%s\n' '{"type":"ready"}'
648 printf '%s\n' '{"type":"open_panel","panel":{"id":"test-panel","title":"Hello Panel","lines":["line one","line two"],"footer":"esc close"}}'677 printf '%s\n' '{"type":"open_panel","panel":{"id":"test-panel","title":"Hello Panel","lines":["line one","line two"],"footer":"esc close"}}'
649 while IFS= read -r line; do678 while IFS= read -r line; do
M packages/agent/extensions/proc_unix.go +3 -3
@@ -7,12 +7,12 @@ import (
7 "syscall"7 "syscall"
8 )8 )
9 9
10-// isolateExtensionProcess moves extension subprocesses out of zot's10+// isolateExtensionProcess moves extension subprocesses out of zut's
11 // foreground process group. Some terminals, including kitty, inspect11 // foreground process group. Some terminals, including kitty, inspect
12 // foreground-process-group members when deciding the cwd for "open new12 // foreground-process-group members when deciding the cwd for "open new
13 // tab/split here". Extensions run with cmd.Dir set to their own install13 // 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 terminal14+// directory, so leaving them in zut's process group can make the terminal
15-// believe the tab cwd is an extension directory even after zot reports the15+// believe the tab cwd is an extension directory even after zut reports the
16 // project cwd with OSC 7.16 // project cwd with OSC 7.
17 func isolateExtensionProcess(cmd *exec.Cmd) {17 func isolateExtensionProcess(cmd *exec.Cmd) {
18 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}18 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
M packages/agent/extensions/tool.go +2 -2
@@ -6,8 +6,8 @@ import (
6 "fmt"6 "fmt"
7 "time"7 "time"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 // extensionTool wraps a single extension-registered tool as a13 // 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) {
23 // Shell-script extension: hello, register one tool, ready, then23 // Shell-script extension: hello, register one tool, ready, then
24 // loop on stdin echoing back tool_call args as tool_result text.24 // loop on stdin echoing back tool_call args as tool_result text.
25 script := `#!/bin/sh25 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"]}'
27 printf '%s\n' '{"type":"register_tool","name":"echo","description":"echo back the args","schema":{"type":"object","properties":{"msg":{"type":"string"}}}}'27 printf '%s\n' '{"type":"register_tool","name":"echo","description":"echo back the args","schema":{"type":"object","properties":{"msg":{"type":"string"}}}}'
28 printf '%s\n' '{"type":"ready"}'28 printf '%s\n' '{"type":"ready"}'
29 while IFS= read -r line; do29 while IFS= read -r line; do
M packages/agent/extproto/extproto.go +12 -9
@@ -1,5 +1,5 @@
1 // Package extproto defines the JSON-over-stdin/stdout wire format1 // Package extproto defines the JSON-over-stdin/stdout wire format
2-// spoken between zot and its extension subprocesses. Both the host2+// spoken between zut and its extension subprocesses. Both the host
3 // (packages/agent/extensions) and the SDK (packages/agent/ext) marshal/3 // (packages/agent/extensions) and the SDK (packages/agent/ext) marshal/
4 // unmarshal the same types, so changes here ripple through both.4 // unmarshal the same types, so changes here ripple through both.
5 //5 //
@@ -7,8 +7,8 @@
7 // boundaries follow newline boundaries; no multi-line JSON.7 // boundaries follow newline boundaries; no multi-line JSON.
8 //8 //
9 // Direction conventions in this file:9 // Direction conventions in this file:
10-// - Type names ending in "FromExt" are sent by the extension to zot.10+// - Type names ending in "FromExt" are sent by the extension to zut.
11-// - Type names ending in "FromHost" are sent by zot to the extension.11+// - Type names ending in "FromHost" are sent by zut to the extension.
12 // - Names without a suffix are direction-neutral payloads or shared12 // - Names without a suffix are direction-neutral payloads or shared
13 // value types.13 // value types.
14 //14 //
@@ -22,7 +22,9 @@ import (
22 "strings"22 "strings"
23 )23 )
24 24
25-const ProtocolVersion = 125+// 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
26 28
27 type Frame struct {29 type Frame struct {
28 Type string `json:"type"`30 Type string `json:"type"`
@@ -30,10 +32,11 @@ type Frame struct {
30 }32 }
31 33
32 type HelloFromExt struct {34 type HelloFromExt struct {
33- Type string `json:"type"`35+ Type string `json:"type"`
34- Name string `json:"name"`36+ ProtocolVersion int `json:"protocol_version"`
35- Version string `json:"version"`37+ Name string `json:"name"`
36- Capabilities []string `json:"capabilities,omitempty"`38+ Version string `json:"version"`
39+ Capabilities []string `json:"capabilities,omitempty"`
37 }40 }
38 41
39 type RegisterCommandFromExt struct {42 type RegisterCommandFromExt struct {
@@ -228,7 +231,7 @@ type ShutdownAckFromExt struct {
228 type HelloAckFromHost struct {231 type HelloAckFromHost struct {
229 Type string `json:"type"`232 Type string `json:"type"`
230 ProtocolVersion int `json:"protocol_version"`233 ProtocolVersion int `json:"protocol_version"`
231- ZotVersion string `json:"zot_version"`234+ ZutVersion string `json:"zut_version"`
232 Provider string `json:"provider"`235 Provider string `json:"provider"`
233 Model string `json:"model"`236 Model string `json:"model"`
234 CWD string `json:"cwd"`237 CWD string `json:"cwd"`
M packages/agent/extupdate.go +8 -8
@@ -13,7 +13,7 @@ import (
13 "time"13 "time"
14 )14 )
15 15
16-// updateAllExtensions iterates $ZOT_HOME/extensions/* and tries to16+// updateAllExtensions iterates $ZUT_HOME/extensions/* and tries to
17 // update each one in place via `git pull --ff-only`. Failures are17 // update each one in place via `git pull --ff-only`. Failures are
18 // per-extension and never abort the loop; the caller can treat any18 // per-extension and never abort the loop; the caller can treat any
19 // error here as advisory only.19 // error here as advisory only.
@@ -38,14 +38,14 @@ import (
38 // remote git URL would be a real footgun. Extension authors are38 // remote git URL would be a real footgun. Extension authors are
39 // expected to commit a working binary (or instruct the user to39 // expected to commit a working binary (or instruct the user to
40 // rebuild manually via /reload-ext + their own build).40 // rebuild manually via /reload-ext + their own build).
41-func updateAllExtensions(zotHome string) {41+func updateAllExtensions(zutHome string) {
42- dir := filepath.Join(zotHome, "extensions")42+ dir := filepath.Join(zutHome, "extensions")
43 entries, err := os.ReadDir(dir)43 entries, err := os.ReadDir(dir)
44 if err != nil {44 if err != nil {
45 if os.IsNotExist(err) {45 if os.IsNotExist(err) {
46 return // no extensions installed; nothing to do46 return // no extensions installed; nothing to do
47 }47 }
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)
49 return49 return
50 }50 }
51 51
@@ -67,7 +67,7 @@ func updateAllExtensions(zotHome string) {
67 }67 }
68 68
69 fmt.Println()69 fmt.Println()
70- fmt.Println("zot update: updating extensions...")70+ fmt.Println("zut update: updating extensions...")
71 71
72 var updated, upToDate, skipped, failed int72 var updated, upToDate, skipped, failed int
73 for _, name := range names {73 for _, name := range names {
@@ -85,7 +85,7 @@ func updateAllExtensions(zotHome string) {
85 }85 }
86 }86 }
87 87
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",
89 updated, upToDate, skipped, failed)89 updated, upToDate, skipped, failed)
90 }90 }
91 91
@@ -180,7 +180,7 @@ func updateOneExtension(extDir, name string) string {
180 func gitStash(ctx context.Context, dir string) (stashRef string, stashed bool, err error) {180 func gitStash(ctx context.Context, dir string) (stashRef string, stashed bool, err error) {
181 // Tag the stash with a recognisable name so the user can find it181 // Tag the stash with a recognisable name so the user can find it
182 // later if pop fails and they want to inspect it.182 // 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())
184 out, err := runGit(ctx, dir, "stash", "push", "--include-untracked", "-m", msg)184 out, err := runGit(ctx, dir, "stash", "push", "--include-untracked", "-m", msg)
185 if err != nil {185 if err != nil {
186 return "", false, fmt.Errorf("%s", strings.TrimSpace(out))186 return "", false, fmt.Errorf("%s", strings.TrimSpace(out))
@@ -196,7 +196,7 @@ func gitStash(ctx context.Context, dir string) (stashRef string, stashed bool, e
196 if lerr == nil {196 if lerr == nil {
197 for _, line := range strings.Split(listOut, "\n") {197 for _, line := range strings.Split(listOut, "\n") {
198 if strings.Contains(line, msg) {198 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"
200 if idx := strings.Index(line, ":"); idx > 0 {200 if idx := strings.Index(line, ":"); idx > 0 {
201 return strings.TrimSpace(line[:idx]), true, nil201 return strings.TrimSpace(line[:idx]), true, nil
202 }202 }
M packages/agent/extupdate_test.go +13 -13
@@ -66,8 +66,8 @@ func mustRun(t *testing.T, dir, name string, args ...string) {
66 }66 }
67 }67 }
68 68
69-// makeFakeZotHome scaffolds $ZOT_HOME/extensions/ and returns the path.69+// makeFakeZutHome scaffolds $ZUT_HOME/extensions/ and returns the path.
70-func makeFakeZotHome(t *testing.T) string {70+func makeFakeZutHome(t *testing.T) string {
71 t.Helper()71 t.Helper()
72 home := t.TempDir()72 home := t.TempDir()
73 if err := os.MkdirAll(filepath.Join(home, "extensions"), 0o755); err != nil {73 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 {
95 // ----- tests -----95 // ----- tests -----
96 96
97 func TestUpdateAllExtensions_NoExtensionsDirectory(t *testing.T) {97 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.
99 // Must not panic and must print nothing alarming.99 // Must not panic and must print nothing alarming.
100 home := t.TempDir()100 home := t.TempDir()
101 updateAllExtensions(home) // no-op101 updateAllExtensions(home) // no-op
102 }102 }
103 103
104 func TestUpdateAllExtensions_EmptyDirectory(t *testing.T) {104 func TestUpdateAllExtensions_EmptyDirectory(t *testing.T) {
105- home := makeFakeZotHome(t)105+ home := makeFakeZutHome(t)
106 updateAllExtensions(home) // no-op, no panic106 updateAllExtensions(home) // no-op, no panic
107 }107 }
108 108
109 func TestUpdateOneExtension_NoManifest(t *testing.T) {109 func TestUpdateOneExtension_NoManifest(t *testing.T) {
110- home := makeFakeZotHome(t)110+ home := makeFakeZutHome(t)
111 dir := filepath.Join(home, "extensions", "noman")111 dir := filepath.Join(home, "extensions", "noman")
112 _ = os.MkdirAll(dir, 0o755)112 _ = os.MkdirAll(dir, 0o755)
113 got := updateOneExtension(dir, "noman")113 got := updateOneExtension(dir, "noman")
@@ -117,7 +117,7 @@ func TestUpdateOneExtension_NoManifest(t *testing.T) {
117 }117 }
118 118
119 func TestUpdateOneExtension_Disabled(t *testing.T) {119 func TestUpdateOneExtension_Disabled(t *testing.T) {
120- home := makeFakeZotHome(t)120+ home := makeFakeZutHome(t)
121 dir := extWithManifest(t, home, "off",121 dir := extWithManifest(t, home, "off",
122 `{"name":"off","version":"1.0.0","exec":"./x","enabled":false}`)122 `{"name":"off","version":"1.0.0","exec":"./x","enabled":false}`)
123 got := updateOneExtension(dir, "off")123 got := updateOneExtension(dir, "off")
@@ -127,7 +127,7 @@ func TestUpdateOneExtension_Disabled(t *testing.T) {
127 }127 }
128 128
129 func TestUpdateOneExtension_NotAGitCheckout(t *testing.T) {129 func TestUpdateOneExtension_NotAGitCheckout(t *testing.T) {
130- home := makeFakeZotHome(t)130+ home := makeFakeZutHome(t)
131 dir := extWithManifest(t, home, "plain", "")131 dir := extWithManifest(t, home, "plain", "")
132 got := updateOneExtension(dir, "plain")132 got := updateOneExtension(dir, "plain")
133 if got != "skipped" {133 if got != "skipped" {
@@ -138,7 +138,7 @@ func TestUpdateOneExtension_NotAGitCheckout(t *testing.T) {
138 func TestUpdateOneExtension_UpToDate(t *testing.T) {138 func TestUpdateOneExtension_UpToDate(t *testing.T) {
139 gitAvailable(t)139 gitAvailable(t)
140 tmp := t.TempDir()140 tmp := t.TempDir()
141- home := makeFakeZotHome(t)141+ home := makeFakeZutHome(t)
142 142
143 // Set up a remote and clone it as the "installed" extension.143 // Set up a remote and clone it as the "installed" extension.
144 remote := filepath.Join(tmp, "remote.git")144 remote := filepath.Join(tmp, "remote.git")
@@ -165,7 +165,7 @@ func TestUpdateOneExtension_UpToDate(t *testing.T) {
165 func TestUpdateOneExtension_PullsNewCommit(t *testing.T) {165 func TestUpdateOneExtension_PullsNewCommit(t *testing.T) {
166 gitAvailable(t)166 gitAvailable(t)
167 tmp := t.TempDir()167 tmp := t.TempDir()
168- home := makeFakeZotHome(t)168+ home := makeFakeZutHome(t)
169 169
170 remote := filepath.Join(tmp, "remote.git")170 remote := filepath.Join(tmp, "remote.git")
171 initBareRepo(t, remote)171 initBareRepo(t, remote)
@@ -201,7 +201,7 @@ func TestUpdateOneExtension_PullsNewCommit(t *testing.T) {
201 func TestUpdateOneExtension_StashesDirtyWorktree(t *testing.T) {201 func TestUpdateOneExtension_StashesDirtyWorktree(t *testing.T) {
202 gitAvailable(t)202 gitAvailable(t)
203 tmp := t.TempDir()203 tmp := t.TempDir()
204- home := makeFakeZotHome(t)204+ home := makeFakeZutHome(t)
205 205
206 remote := filepath.Join(tmp, "remote.git")206 remote := filepath.Join(tmp, "remote.git")
207 initBareRepo(t, remote)207 initBareRepo(t, remote)
@@ -244,7 +244,7 @@ func TestUpdateOneExtension_StashesDirtyWorktree(t *testing.T) {
244 func TestUpdateOneExtension_DivergedFails(t *testing.T) {244 func TestUpdateOneExtension_DivergedFails(t *testing.T) {
245 gitAvailable(t)245 gitAvailable(t)
246 tmp := t.TempDir()246 tmp := t.TempDir()
247- home := makeFakeZotHome(t)247+ home := makeFakeZutHome(t)
248 248
249 remote := filepath.Join(tmp, "remote.git")249 remote := filepath.Join(tmp, "remote.git")
250 initBareRepo(t, remote)250 initBareRepo(t, remote)
@@ -281,7 +281,7 @@ func TestUpdateOneExtension_DivergedFails(t *testing.T) {
281 func TestUpdateOneExtension_BadRemoteFails(t *testing.T) {281 func TestUpdateOneExtension_BadRemoteFails(t *testing.T) {
282 gitAvailable(t)282 gitAvailable(t)
283 tmp := t.TempDir()283 tmp := t.TempDir()
284- home := makeFakeZotHome(t)284+ home := makeFakeZutHome(t)
285 285
286 // Init a repo with an "origin" that points at a non-existent path.286 // Init a repo with an "origin" that points at a non-existent path.
287 extDir := filepath.Join(home, "extensions", "bad")287 extDir := filepath.Join(home, "extensions", "bad")
@@ -312,7 +312,7 @@ func TestUpdateOneExtension_BadRemoteFails(t *testing.T) {
312 func TestUpdateAllExtensions_MixedSet(t *testing.T) {312 func TestUpdateAllExtensions_MixedSet(t *testing.T) {
313 gitAvailable(t)313 gitAvailable(t)
314 tmp := t.TempDir()314 tmp := t.TempDir()
315- home := makeFakeZotHome(t)315+ home := makeFakeZutHome(t)
316 316
317 // 1 plain non-git extension (skipped)317 // 1 plain non-git extension (skipped)
318 extWithManifest(t, home, "plain", "")318 extWithManifest(t, home, "plain", "")
M packages/agent/fast_mode_test.go +7 -7
@@ -6,7 +6,7 @@ import (
6 "strings"6 "strings"
7 "testing"7 "testing"
8 8
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10 )10 )
11 11
12 func TestParseArgsFastModeChildFlag(t *testing.T) {12 func TestParseArgsFastModeChildFlag(t *testing.T) {
@@ -23,7 +23,7 @@ func TestParseArgsFastModeChildFlag(t *testing.T) {
23 }23 }
24 24
25 func TestResolveFastModeDefaultsOff(t *testing.T) {25 func TestResolveFastModeDefaultsOff(t *testing.T) {
26- t.Setenv("ZOT_HOME", t.TempDir())26+ t.Setenv("ZUT_HOME", t.TempDir())
27 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5"}); err != nil {27 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5"}); err != nil {
28 t.Fatal(err)28 t.Fatal(err)
29 }29 }
@@ -38,7 +38,7 @@ func TestResolveFastModeDefaultsOff(t *testing.T) {
38 }38 }
39 39
40 func TestResolveFastModeFromConfig(t *testing.T) {40 func TestResolveFastModeFromConfig(t *testing.T) {
41- t.Setenv("ZOT_HOME", t.TempDir())41+ t.Setenv("ZUT_HOME", t.TempDir())
42 enabled := true42 enabled := true
43 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", FastMode: &enabled}); err != nil {43 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", FastMode: &enabled}); err != nil {
44 t.Fatal(err)44 t.Fatal(err)
@@ -54,7 +54,7 @@ func TestResolveFastModeFromConfig(t *testing.T) {
54 }54 }
55 55
56 func TestResolveFastModeRemainsEnabledForUnsupportedProviderUntilRequest(t *testing.T) {56 func TestResolveFastModeRemainsEnabledForUnsupportedProviderUntilRequest(t *testing.T) {
57- t.Setenv("ZOT_HOME", t.TempDir())57+ t.Setenv("ZUT_HOME", t.TempDir())
58 enabled := true58 enabled := true
59 if err := SaveConfig(Config{Provider: "anthropic", Model: "claude-sonnet-4-5", FastMode: &enabled}); err != nil {59 if err := SaveConfig(Config{Provider: "anthropic", Model: "claude-sonnet-4-5", FastMode: &enabled}); err != nil {
60 t.Fatal(err)60 t.Fatal(err)
@@ -70,7 +70,7 @@ func TestResolveFastModeRemainsEnabledForUnsupportedProviderUntilRequest(t *test
70 }70 }
71 71
72 func TestResolveFastModeRequiresExplicitChildOverride(t *testing.T) {72 func TestResolveFastModeRequiresExplicitChildOverride(t *testing.T) {
73- t.Setenv("ZOT_HOME", t.TempDir())73+ t.Setenv("ZUT_HOME", t.TempDir())
74 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5"}); err != nil {74 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5"}); err != nil {
75 t.Fatal(err)75 t.Fatal(err)
76 }76 }
@@ -85,7 +85,7 @@ func TestResolveFastModeRequiresExplicitChildOverride(t *testing.T) {
85 }85 }
86 86
87 func TestResolveFastModeExplicitChildDisableOverridesConfig(t *testing.T) {87 func TestResolveFastModeExplicitChildDisableOverridesConfig(t *testing.T) {
88- t.Setenv("ZOT_HOME", t.TempDir())88+ t.Setenv("ZUT_HOME", t.TempDir())
89 enabled := true89 enabled := true
90 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", FastMode: &enabled}); err != nil {90 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", FastMode: &enabled}); err != nil {
91 t.Fatal(err)91 t.Fatal(err)
@@ -105,7 +105,7 @@ func TestResolveFastModeExplicitChildDisableOverridesConfig(t *testing.T) {
105 }105 }
106 106
107 func TestConfigSettingsStorePersistsFastMode(t *testing.T) {107 func TestConfigSettingsStorePersistsFastMode(t *testing.T) {
108- t.Setenv("ZOT_HOME", t.TempDir())108+ t.Setenv("ZUT_HOME", t.TempDir())
109 if err := SaveConfig(Config{Theme: "dark"}); err != nil {109 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
110 t.Fatal(err)110 t.Fatal(err)
111 }111 }
M packages/agent/instruction_context.go +2 -2
@@ -3,8 +3,8 @@ package agent
3 import (3 import (
4 "sort"4 "sort"
5 5
6- "github.com/patriceckhart/zot/packages/agent/extensions"6+ "github.com/bnema/zut/packages/agent/extensions"
7- "github.com/patriceckhart/zot/packages/agent/skills"7+ "github.com/bnema/zut/packages/agent/skills"
8 )8 )
9 9
10 // instructionContextPaths returns the loaded instruction paths in effective10 // instructionContextPaths returns the loaded instruction paths in effective
M packages/agent/instruction_context_test.go +2 -2
@@ -4,8 +4,8 @@ import (
4 "reflect"4 "reflect"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/agent/extensions"7+ "github.com/bnema/zut/packages/agent/extensions"
8- "github.com/patriceckhart/zot/packages/agent/skills"8+ "github.com/bnema/zut/packages/agent/skills"
9 )9 )
10 10
11 func TestInstructionContextPathsPreserveLoadOrder(t *testing.T) {11 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 {
164 },164 },
165 },165 },
166 "initializationOptions": c.options.InitializationOptions,166 "initializationOptions": c.options.InitializationOptions,
167- "clientInfo": map[string]any{"name": "zot", "version": "lsp"},167+ "clientInfo": map[string]any{"name": "zut", "version": "lsp"},
168 }168 }
169 result, err := c.Request(ctx, "initialize", params)169 result, err := c.Request(ctx, "initialize", params)
170 if err != nil {170 if err != nil {
@@ -273,7 +273,7 @@ func (c *Client) DidOpen(path, languageID, text string) error {
273 }273 }
274 274
275 // DidChange sends a full-content change, which avoids version/range drift275 // 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.
277 func (c *Client) DidChange(path, text string) error {277 func (c *Client) DidChange(path, text string) error {
278 uri, err := pathToURI(path)278 uri, err := pathToURI(path)
279 if err != nil {279 if err != nil {
M packages/agent/lsp/config.go +10 -10
@@ -64,14 +64,14 @@ func BuiltinServers() []ServerConfig {
64 64
65 // LoadConfig reads the global and project lsp.json files. Project files are65 // LoadConfig reads the global and project lsp.json files. Project files are
66 // applied after the global file; within a project, lsp.json, .lsp.json, and66 // 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.
68 func LoadConfig(cwd string) (Config, error) {68 func LoadConfig(cwd string) (Config, error) {
69 cwd, err := absDir(cwd)69 cwd, err := absDir(cwd)
70 if err != nil {70 if err != nil {
71 return Config{}, err71 return Config{}, err
72 }72 }
73 var files []string73 var files []string
74- if home := zotHome(); home != "" {74+ if home := zutHome(); home != "" {
75 files = append(files, filepath.Join(home, "lsp.json"))75 files = append(files, filepath.Join(home, "lsp.json"))
76 }76 }
77 files = append(files,77 files = append(files,
@@ -79,7 +79,7 @@ func LoadConfig(cwd string) (Config, error) {
79 filepath.Join(cwd, ".omp", "lsp.json"),79 filepath.Join(cwd, ".omp", "lsp.json"),
80 filepath.Join(cwd, "lsp.json"),80 filepath.Join(cwd, "lsp.json"),
81 filepath.Join(cwd, ".lsp.json"),81 filepath.Join(cwd, ".lsp.json"),
82- filepath.Join(cwd, ".zot", "lsp.json"),82+ filepath.Join(cwd, ".zut", "lsp.json"),
83 )83 )
84 84
85 objects := make(map[string]map[string]json.RawMessage)85 objects := make(map[string]map[string]json.RawMessage)
@@ -341,12 +341,12 @@ func absDir(cwd string) (string, error) {
341 return filepath.Clean(abs), nil341 return filepath.Clean(abs), nil
342 }342 }
343 343
344-func zotHome() string {344+func zutHome() string {
345- if value := strings.TrimSpace(os.Getenv("ZOT_HOME")); value != "" {345+ if value := strings.TrimSpace(os.Getenv("ZUT_HOME")); value != "" {
346 return value346 return value
347 }347 }
348 if value := strings.TrimSpace(os.Getenv("XDG_STATE_HOME")); value != "" {348 if value := strings.TrimSpace(os.Getenv("XDG_STATE_HOME")); value != "" {
349- return filepath.Join(value, "zot")349+ return filepath.Join(value, "zut")
350 }350 }
351 switch runtime.GOOS {351 switch runtime.GOOS {
352 case "darwin":352 case "darwin":
@@ -354,17 +354,17 @@ func zotHome() string {
354 if home == "" {354 if home == "" {
355 return ""355 return ""
356 }356 }
357- return filepath.Join(home, "Library", "Application Support", "zot")357+ return filepath.Join(home, "Library", "Application Support", "zut")
358 case "windows":358 case "windows":
359 if value := strings.TrimSpace(os.Getenv("LOCALAPPDATA")); value != "" {359 if value := strings.TrimSpace(os.Getenv("LOCALAPPDATA")); value != "" {
360- return filepath.Join(value, "zot")360+ return filepath.Join(value, "zut")
361 }361 }
362 }362 }
363 home, _ := os.UserHomeDir()363 home, _ := os.UserHomeDir()
364 if home == "" {364 if home == "" {
365 return ""365 return ""
366 }366 }
367- return filepath.Join(home, ".local", "state", "zot")367+ return filepath.Join(home, ".local", "state", "zut")
368 }368 }
369 369
370 func providerIDs(data []byte) map[string]struct{} {370 func providerIDs(data []byte) map[string]struct{} {
@@ -480,7 +480,7 @@ func strconvQuote(s string) string {
480 }480 }
481 481
482 // flattenProviderObject adapts nested provider shapes to the flat482 // flattenProviderObject adapts nested provider shapes to the flat
483-// ServerConfig fields used by zot. Unknown provider metadata remains483+// ServerConfig fields used by zut. Unknown provider metadata remains
484 // harmless JSON and is ignored by decodeServer.484 // harmless JSON and is ignored by decodeServer.
485 func flattenProviderObject(object map[string]json.RawMessage) {485 func flattenProviderObject(object map[string]json.RawMessage) {
486 if _, exists := object["command"]; !exists {486 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
187 }187 }
188 188
189 func writeTextEdits(path string, out []byte, mode os.FileMode) error {189 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-*")
191 if err != nil {191 if err != nil {
192 return err192 return err
193 }193 }
M packages/agent/lsp/lsp_test.go +16 -16
@@ -16,7 +16,7 @@ import (
16 )16 )
17 17
18 func TestFakeLSPProcess(t *testing.T) {18 func TestFakeLSPProcess(t *testing.T) {
19- if os.Getenv("ZOT_FAKE_LSP") != "1" {19+ if os.Getenv("ZUT_FAKE_LSP") != "1" {
20 return20 return
21 }21 }
22 reader := bufio.NewReader(os.Stdin)22 reader := bufio.NewReader(os.Stdin)
@@ -56,7 +56,7 @@ func TestFakeLSPProcess(t *testing.T) {
56 func TestClientFakeStdioRPC(t *testing.T) {56 func TestClientFakeStdioRPC(t *testing.T) {
57 root := t.TempDir()57 root := t.TempDir()
58 script := os.Args[0]58 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"}}
60 diagnostics := make(chan []Diagnostic, 1)60 diagnostics := make(chan []Diagnostic, 1)
61 client, err := NewClientWithContext(context.Background(), spec, root, ClientOptions{OnDiagnostics: func(value []Diagnostic) { diagnostics <- value }})61 client, err := NewClientWithContext(context.Background(), spec, root, ClientOptions{OnDiagnostics: func(value []Diagnostic) { diagnostics <- value }})
62 if err != nil {62 if err != nil {
@@ -157,25 +157,25 @@ func TestFramingRoundTripWithAdditionalHeader(t *testing.T) {
157 }157 }
158 }158 }
159 159
160-func TestZotHomeEnvironmentPrecedence(t *testing.T) {160+func TestZutHomeEnvironmentPrecedence(t *testing.T) {
161- configuredHome := filepath.Join(t.TempDir(), "zot")161+ configuredHome := filepath.Join(t.TempDir(), "zut")
162 xdgHome := filepath.Join(t.TempDir(), "state")162 xdgHome := filepath.Join(t.TempDir(), "state")
163- t.Setenv("ZOT_HOME", configuredHome)163+ t.Setenv("ZUT_HOME", configuredHome)
164 t.Setenv("XDG_STATE_HOME", xdgHome)164 t.Setenv("XDG_STATE_HOME", xdgHome)
165- if got := zotHome(); got != configuredHome {165+ if got := zutHome(); got != configuredHome {
166- t.Fatalf("ZOT_HOME path = %q, want %q", got, configuredHome)166+ t.Fatalf("ZUT_HOME path = %q, want %q", got, configuredHome)
167 }167 }
168- t.Setenv("ZOT_HOME", "")168+ t.Setenv("ZUT_HOME", "")
169- if got := zotHome(); got != filepath.Join(xdgHome, "zot") {169+ if got := zutHome(); got != filepath.Join(xdgHome, "zut") {
170- t.Fatalf("XDG_STATE_HOME path = %q, want %q", got, filepath.Join(xdgHome, "zot"))170+ t.Fatalf("XDG_STATE_HOME path = %q, want %q", got, filepath.Join(xdgHome, "zut"))
171 }171 }
172 }172 }
173 173
174 func TestLoadConfigMergesGlobalProjectAndBuiltins(t *testing.T) {174 func TestLoadConfigMergesGlobalProjectAndBuiltins(t *testing.T) {
175 root := t.TempDir()175 root := t.TempDir()
176- zotHome := t.TempDir()176+ zutHome := t.TempDir()
177- t.Setenv("ZOT_HOME", zotHome)177+ t.Setenv("ZUT_HOME", zutHome)
178- if err := os.WriteFile(filepath.Join(zotHome, "lsp.json"), []byte(`{"servers":{"gopls":{"settings":{"global":true},"args":["--global"]}}}`), 0o644); err != nil {178+ if err := os.WriteFile(filepath.Join(zutHome, "lsp.json"), []byte(`{"servers":{"gopls":{"settings":{"global":true},"args":["--global"]}}}`), 0o644); err != nil {
179 t.Fatal(err)179 t.Fatal(err)
180 }180 }
181 if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n"), 0o644); err != nil {181 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) {
214 214
215 func TestLoadConfigAcceptsProviderArrays(t *testing.T) {215 func TestLoadConfigAcceptsProviderArrays(t *testing.T) {
216 root := t.TempDir()216 root := t.TempDir()
217- zotHome := t.TempDir()217+ zutHome := t.TempDir()
218- t.Setenv("ZOT_HOME", zotHome)218+ t.Setenv("ZUT_HOME", zutHome)
219 if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n"), 0o644); err != nil {219 if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n"), 0o644); err != nil {
220 t.Fatal(err)220 t.Fatal(err)
221 }221 }
@@ -245,7 +245,7 @@ func TestLoadConfigAcceptsProviderArrays(t *testing.T) {
245 245
246 func TestLoadConfigAutoDetectFalseKeepsExplicitProviders(t *testing.T) {246 func TestLoadConfigAutoDetectFalseKeepsExplicitProviders(t *testing.T) {
247 root := t.TempDir()247 root := t.TempDir()
248- t.Setenv("ZOT_HOME", t.TempDir())248+ t.Setenv("ZUT_HOME", t.TempDir())
249 if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n"), 0o644); err != nil {249 if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n"), 0o644); err != nil {
250 t.Fatal(err)250 t.Fatal(err)
251 }251 }
M packages/agent/lsp/protocol.go +1 -1
@@ -26,7 +26,7 @@ type TextEdit struct {
26 NewText string `json:"newText"`26 NewText string `json:"newText"`
27 }27 }
28 28
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.
30 // Resource operations in DocumentChanges are deliberately rejected.30 // Resource operations in DocumentChanges are deliberately rejected.
31 type WorkspaceEdit struct {31 type WorkspaceEdit struct {
32 Changes map[string][]TextEdit `json:"changes,omitempty"`32 Changes map[string][]TextEdit `json:"changes,omitempty"`
M packages/agent/modelsync.go +13 -13
@@ -8,17 +8,17 @@ import (
8 "strings"8 "strings"
9 "time"9 "time"
10 10
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12 )12 )
13 13
14 // ModelCachePath returns the on-disk location of the merged model cache.14 // ModelCachePath returns the on-disk location of the merged model cache.
15 func ModelCachePath() string {15 func ModelCachePath() string {
16- return filepath.Join(ZotHome(), "models-cache.json")16+ return filepath.Join(ZutHome(), "models-cache.json")
17 }17 }
18 18
19 // UserModelsPath returns the path to the user's models.json override.19 // UserModelsPath returns the path to the user's models.json override.
20 func UserModelsPath() string {20 func UserModelsPath() string {
21- return filepath.Join(ZotHome(), "models.json")21+ return filepath.Join(ZutHome(), "models.json")
22 }22 }
23 23
24 // LoadCachedModels loads the cache file and applies it to the provider24 // LoadCachedModels loads the cache file and applies it to the provider
@@ -34,7 +34,7 @@ func LoadCachedModels() {
34 }34 }
35 }35 }
36 36
37-// LoadUserModels reads $ZOT_HOME/models.json and merges any user-defined37+// LoadUserModels reads $ZUT_HOME/models.json and merges any user-defined
38 // models into the active catalog. User models take highest precedence.38 // models into the active catalog. User models take highest precedence.
39 // Any validation issues (bad provider id, empty model id, malformed39 // Any validation issues (bad provider id, empty model id, malformed
40 // JSON, negative widths) are surfaced as one warning per line on stderr;40 // JSON, negative widths) are surfaced as one warning per line on stderr;
@@ -42,14 +42,14 @@ func LoadCachedModels() {
42 func LoadUserModels() {42 func LoadUserModels() {
43 models, warnings := provider.LoadUserModelsWithWarnings(UserModelsPath())43 models, warnings := provider.LoadUserModelsWithWarnings(UserModelsPath())
44 for _, w := range warnings {44 for _, w := range warnings {
45- fmt.Fprintln(os.Stderr, "zot:", w)45+ fmt.Fprintln(os.Stderr, "zut:", w)
46 }46 }
47 provider.SetUserModels(models)47 provider.SetUserModels(models)
48 }48 }
49 49
50 // isGatewayProvider returns true for providers whose OpenAI-compatible50 // isGatewayProvider returns true for providers whose OpenAI-compatible
51-// endpoint can accept routed model IDs that are not present in zot's local51+// endpoint can accept routed model IDs that are not present in zut's local
52-// catalog. Vercel AI Gateway is intentionally not listed here: zot currently52+// catalog. Vercel AI Gateway is intentionally not listed here: zut currently
53 // talks to it through the Anthropic-compatible client, which still requires53 // talks to it through the Anthropic-compatible client, which still requires
54 // catalog metadata for request shaping.54 // catalog metadata for request shaping.
55 func isGatewayProvider(prov string) bool {55 func isGatewayProvider(prov string) bool {
@@ -81,20 +81,20 @@ func isGatewayRoutedModelID(model string) bool {
81 // half-applied switch) -> reset model to the provider's default.81 // half-applied switch) -> reset model to the provider's default.
82 //82 //
83 // Gateway providers are exempt from the cross-provider model check for routed83 // 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.
85 //85 //
86 // Silent on success; one stderr line per repair. Errors loading or86 // Silent on success; one stderr line per repair. Errors loading or
87 // saving the file are non-fatal — the caller continues with defaults.87 // saving the file are non-fatal — the caller continues with defaults.
88 func ValidateAndRepairConfig() {88 func ValidateAndRepairConfig() {
89 cfg, err := LoadConfig()89 cfg, err := LoadConfig()
90 if err != nil {90 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)
92 return92 return
93 }93 }
94 changed := false94 changed := false
95 95
96 if cfg.Provider != "" && !isKnownProvider(cfg.Provider) {96 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)
98 cfg.Provider = "anthropic"98 cfg.Provider = "anthropic"
99 cfg.Model = ""99 cfg.Model = ""
100 changed = true100 changed = true
@@ -110,7 +110,7 @@ func ValidateAndRepairConfig() {
110 } else if m, err := provider.FindModel("", cfg.Model); err == nil {110 } else if m, err := provider.FindModel("", cfg.Model); err == nil {
111 fix := defaultModelForProvider(cfg.Provider)111 fix := defaultModelForProvider(cfg.Provider)
112 fmt.Fprintf(os.Stderr,112 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",
114 cfg.Model, m.Provider, cfg.Provider, fix)114 cfg.Model, m.Provider, cfg.Provider, fix)
115 cfg.Model = fix115 cfg.Model = fix
116 changed = true116 changed = true
@@ -118,7 +118,7 @@ func ValidateAndRepairConfig() {
118 // Model id not in any catalog. Reset to provider's default.118 // Model id not in any catalog. Reset to provider's default.
119 fix := defaultModelForProvider(cfg.Provider)119 fix := defaultModelForProvider(cfg.Provider)
120 fmt.Fprintf(os.Stderr,120 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",
122 cfg.Model, fix)122 cfg.Model, fix)
123 cfg.Model = fix123 cfg.Model = fix
124 changed = true124 changed = true
@@ -128,7 +128,7 @@ func ValidateAndRepairConfig() {
128 128
129 if changed {129 if changed {
130 if err := SaveConfig(cfg); err != nil {130 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)
132 }132 }
133 }133 }
134 }134 }
M packages/agent/modelsync_test.go +9 -9
@@ -10,7 +10,7 @@ import (
10 "path/filepath"10 "path/filepath"
11 "testing"11 "testing"
12 12
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 func TestRefreshLlamaCPPModelsAddsOnlyLoadedModels(t *testing.T) {16 func TestRefreshLlamaCPPModelsAddsOnlyLoadedModels(t *testing.T) {
@@ -23,7 +23,7 @@ func TestRefreshLlamaCPPModelsAddsOnlyLoadedModels(t *testing.T) {
23 }))23 }))
24 defer server.Close()24 defer server.Close()
25 25
26- t.Setenv("ZOT_HOME", t.TempDir())26+ t.Setenv("ZUT_HOME", t.TempDir())
27 t.Setenv("LLAMA_BASE_URL", "")27 t.Setenv("LLAMA_BASE_URL", "")
28 if err := AuthStoreFor().SetEndpointCredential(provider.LlamaCPPProviderID, server.URL, ""); err != nil {28 if err := AuthStoreFor().SetEndpointCredential(provider.LlamaCPPProviderID, server.URL, ""); err != nil {
29 t.Fatal(err)29 t.Fatal(err)
@@ -52,7 +52,7 @@ func TestRefreshLlamaCPPModelsAddsOnlyLoadedModels(t *testing.T) {
52 // model to anthropic's default and persist.52 // model to anthropic's default and persist.
53 func TestValidateAndRepairConfig_MismatchedPair(t *testing.T) {53 func TestValidateAndRepairConfig_MismatchedPair(t *testing.T) {
54 home := t.TempDir()54 home := t.TempDir()
55- t.Setenv("ZOT_HOME", home)55+ t.Setenv("ZUT_HOME", home)
56 56
57 must := func(c Config) {57 must := func(c Config) {
58 t.Helper()58 t.Helper()
@@ -85,7 +85,7 @@ func TestValidateAndRepairConfig_MismatchedPair(t *testing.T) {
85 // (e.g. user removed it from a previous build).85 // (e.g. user removed it from a previous build).
86 func TestValidateAndRepairConfig_UnknownProvider(t *testing.T) {86 func TestValidateAndRepairConfig_UnknownProvider(t *testing.T) {
87 home := t.TempDir()87 home := t.TempDir()
88- t.Setenv("ZOT_HOME", home)88+ t.Setenv("ZUT_HOME", home)
89 89
90 b, _ := json.Marshal(Config{Provider: "made-up-provider", Model: "some-model"})90 b, _ := json.Marshal(Config{Provider: "made-up-provider", Model: "some-model"})
91 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)91 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
@@ -106,7 +106,7 @@ func TestValidateAndRepairConfig_UnknownProvider(t *testing.T) {
106 // longer in the catalog.106 // longer in the catalog.
107 func TestValidateAndRepairConfig_UnknownModel(t *testing.T) {107 func TestValidateAndRepairConfig_UnknownModel(t *testing.T) {
108 home := t.TempDir()108 home := t.TempDir()
109- t.Setenv("ZOT_HOME", home)109+ t.Setenv("ZUT_HOME", home)
110 110
111 b, _ := json.Marshal(Config{Provider: "anthropic", Model: "claude-deleted-model"})111 b, _ := json.Marshal(Config{Provider: "anthropic", Model: "claude-deleted-model"})
112 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)112 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
@@ -124,7 +124,7 @@ func TestValidateAndRepairConfig_UnknownModel(t *testing.T) {
124 124
125 func TestValidateAndRepairConfig_DuplicateModelIDValidForConfiguredProvider(t *testing.T) {125 func TestValidateAndRepairConfig_DuplicateModelIDValidForConfiguredProvider(t *testing.T) {
126 home := t.TempDir()126 home := t.TempDir()
127- t.Setenv("ZOT_HOME", home)127+ t.Setenv("ZUT_HOME", home)
128 128
129 b, _ := json.Marshal(Config{Provider: "openai-codex", Model: "gpt-5.5"})129 b, _ := json.Marshal(Config{Provider: "openai-codex", Model: "gpt-5.5"})
130 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)130 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
@@ -142,7 +142,7 @@ func TestValidateAndRepairConfig_DuplicateModelIDValidForConfiguredProvider(t *t
142 142
143 func TestValidateAndRepairConfig_OpenRouterPreservesRoutedModelID(t *testing.T) {143 func TestValidateAndRepairConfig_OpenRouterPreservesRoutedModelID(t *testing.T) {
144 home := t.TempDir()144 home := t.TempDir()
145- t.Setenv("ZOT_HOME", home)145+ t.Setenv("ZUT_HOME", home)
146 146
147 want := "deepseek/deepseek-v4-flash"147 want := "deepseek/deepseek-v4-flash"
148 b, _ := json.Marshal(Config{Provider: "openrouter", Model: want})148 b, _ := json.Marshal(Config{Provider: "openrouter", Model: want})
@@ -161,7 +161,7 @@ func TestValidateAndRepairConfig_OpenRouterPreservesRoutedModelID(t *testing.T)
161 161
162 func TestValidateAndRepairConfig_GatewayPlainUnknownModelStillRepairs(t *testing.T) {162 func TestValidateAndRepairConfig_GatewayPlainUnknownModelStillRepairs(t *testing.T) {
163 home := t.TempDir()163 home := t.TempDir()
164- t.Setenv("ZOT_HOME", home)164+ t.Setenv("ZUT_HOME", home)
165 165
166 b, _ := json.Marshal(Config{Provider: "openrouter", Model: "not-a-routed-model"})166 b, _ := json.Marshal(Config{Provider: "openrouter", Model: "not-a-routed-model"})
167 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)167 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
@@ -180,7 +180,7 @@ func TestValidateAndRepairConfig_GatewayPlainUnknownModelStillRepairs(t *testing
180 // TestValidateAndRepairConfig_HappyPath leaves a valid config alone.180 // TestValidateAndRepairConfig_HappyPath leaves a valid config alone.
181 func TestValidateAndRepairConfig_HappyPath(t *testing.T) {181 func TestValidateAndRepairConfig_HappyPath(t *testing.T) {
182 home := t.TempDir()182 home := t.TempDir()
183- t.Setenv("ZOT_HOME", home)183+ t.Setenv("ZUT_HOME", home)
184 184
185 b, _ := json.Marshal(Config{Provider: "anthropic", Model: "claude-sonnet-4-5"})185 b, _ := json.Marshal(Config{Provider: "anthropic", Model: "claude-sonnet-4-5"})
186 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)186 _ = os.WriteFile(filepath.Join(home, "config.json"), b, 0o644)
M packages/agent/modes/alert_test.go +4 -4
@@ -8,10 +8,10 @@ import (
8 "testing"8 "testing"
9 "time"9 "time"
10 10
11- "github.com/patriceckhart/zot/packages/agent/extproto"11+ "github.com/bnema/zut/packages/agent/extproto"
12- "github.com/patriceckhart/zot/packages/core"12+ "github.com/bnema/zut/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14- "github.com/patriceckhart/zot/packages/tui"14+ "github.com/bnema/zut/packages/tui"
15 )15 )
16 16
17 type alertTestTerminal struct {17 type alertTestTerminal struct {
M packages/agent/modes/auto_compact_test.go +1 -1
@@ -3,7 +3,7 @@ package modes
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/provider"6+ "github.com/bnema/zut/packages/provider"
7 )7 )
8 8
9 func TestNormalizeAutoCompactThreshold(t *testing.T) {9 func TestNormalizeAutoCompactThreshold(t *testing.T) {
M packages/agent/modes/auto_subagents_policy_test.go +3 -3
@@ -6,9 +6,9 @@ import (
6 "testing"6 "testing"
7 "time"7 "time"
8 8
9- "github.com/patriceckhart/zot/packages/agent/subagents"9+ "github.com/bnema/zut/packages/agent/subagents"
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12 )12 )
13 13
14 func TestAutoSubagentsSettingsDisableUnavailablePolicy(t *testing.T) {14 func TestAutoSubagentsSettingsDisableUnavailablePolicy(t *testing.T) {
M packages/agent/modes/auto_subagents_test.go +3 -3
@@ -8,9 +8,9 @@ import (
8 "testing"8 "testing"
9 "time"9 "time"
10 10
11- "github.com/patriceckhart/zot/packages/agent/subagents"11+ "github.com/bnema/zut/packages/agent/subagents"
12- "github.com/patriceckhart/zot/packages/core"12+ "github.com/bnema/zut/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 func TestAutoSubagentsSystemPromptTogglesProfileManifestWithDelegationGuidance(t *testing.T) {16 func TestAutoSubagentsSystemPromptTogglesProfileManifestWithDelegationGuidance(t *testing.T) {
M packages/agent/modes/bot/adapter.go +1 -1
@@ -7,7 +7,7 @@ package bot
7 import (7 import (
8 "context"8 "context"
9 9
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 // InboundMessage is a protocol-normalised message from a user.13 // InboundMessage is a protocol-normalised message from a user.
M packages/agent/modes/bot/runner.go +4 -4
@@ -8,8 +8,8 @@ import (
8 "strings"8 "strings"
9 "sync"9 "sync"
10 10
11- "github.com/patriceckhart/zot/packages/core"11+ "github.com/bnema/zut/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"12+ "github.com/bnema/zut/packages/provider"
13 )13 )
14 14
15 // stderr is a tiny hook so tests can redirect bot logging.15 // stderr is a tiny hook so tests can redirect bot logging.
@@ -17,7 +17,7 @@ var stderr = func() io.Writer { return os.Stderr }
17 17
18 // Config holds runner-level settings that are protocol-independent.18 // Config holds runner-level settings that are protocol-independent.
19 type Config struct {19 type Config struct {
20- ZotHome string20+ ZutHome string
21 Provider string21 Provider string
22 Model string22 Model string
23 AuthMethod string23 AuthMethod string
@@ -107,7 +107,7 @@ func (r *Runner) handleCommand(cmd Command, msg InboundMessage) {
107 switch cmd {107 switch cmd {
108 case CmdStart, CmdHelp:108 case CmdStart, CmdHelp:
109 _ = r.adapter.Send(context.Background(), msg.ChannelID,109 _ = 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.",
111 SendOptions{ReplyToMessageID: msg.MessageID})111 SendOptions{ReplyToMessageID: msg.MessageID})
112 case CmdStatus:112 case CmdStatus:
113 r.sendStatus(msg.ChannelID, msg.MessageID)113 r.sendStatus(msg.ChannelID, msg.MessageID)
M packages/agent/modes/bot/runner_test.go +2 -2
@@ -6,8 +6,8 @@ import (
6 "testing"6 "testing"
7 "time"7 "time"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 type testAdapter struct{}13 type testAdapter struct{}
M packages/agent/modes/bot/status.go +1 -1
@@ -5,7 +5,7 @@ import (
5 "os"5 "os"
6 "strings"6 "strings"
7 7
8- "github.com/patriceckhart/zot/packages/provider"8+ "github.com/bnema/zut/packages/provider"
9 )9 )
10 10
11 // StatusSnapshot is the small cross-host state bundle rendered for11 // StatusSnapshot is the small cross-host state bundle rendered for
M packages/agent/modes/btw_dialog.go +3 -3
@@ -6,9 +6,9 @@ import (
6 "strings"6 "strings"
7 "sync"7 "sync"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11- "github.com/patriceckhart/zot/packages/tui"11+ "github.com/bnema/zut/packages/tui"
12 )12 )
13 13
14 // btwTurn is one user/assistant pair within a side chat. Kept14 // 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 (
9 "testing"9 "testing"
10 "time"10 "time"
11 11
12- "github.com/patriceckhart/zot/packages/core"12+ "github.com/bnema/zut/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14- "github.com/patriceckhart/zot/packages/tui"14+ "github.com/bnema/zut/packages/tui"
15 )15 )
16 16
17 type btwToolTestClient struct {17 type btwToolTestClient struct {
M packages/agent/modes/changelog_dialog.go +3 -3
@@ -4,11 +4,11 @@ import (
4 "fmt"4 "fmt"
5 "strings"5 "strings"
6 6
7- "github.com/patriceckhart/zot/packages/tui"7+ "github.com/bnema/zut/packages/tui"
8 )8 )
9 9
10 // changelogDialog is the one-shot release-notes overlay shown the10 // changelogDialog is the one-shot release-notes overlay shown the
11-// first time a user launches a new zot version. The body is the11+// first time a user launches a new zut version. The body is the
12 // markdown from the GitHub release page, rendered through the same12 // markdown from the GitHub release page, rendered through the same
13 // pipeline used for assistant messages so code fences + bold +13 // pipeline used for assistant messages so code fences + bold +
14 // links look right.14 // links look right.
@@ -76,7 +76,7 @@ func (d *changelogDialog) Render(th tui.Theme, width int) []string {
76 if !d.Active() {76 if !d.Active() {
77 return nil77 return nil
78 }78 }
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)
80 out := []string{frameHeaderColor(th, title, width, th.Accent)}80 out := []string{frameHeaderColor(th, title, width, th.Accent)}
81 if d.url != "" {81 if d.url != "" {
82 out = append(out, " "+th.FGColor(th.Muted, d.url))82 out = append(out, " "+th.FGColor(th.Muted, d.url))
M packages/agent/modes/changelog_dialog_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "strings"4 "strings"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/tui"7+ "github.com/bnema/zut/packages/tui"
8 )8 )
9 9
10 func TestChangelogDialogWrapsReleaseNotes(t *testing.T) {10 func TestChangelogDialogWrapsReleaseNotes(t *testing.T) {
M packages/agent/modes/clipboard_images.go +1 -1
@@ -3,7 +3,7 @@ package modes
3 import (3 import (
4 "strings"4 "strings"
5 5
6- "github.com/patriceckhart/zot/packages/provider"6+ "github.com/bnema/zut/packages/provider"
7 )7 )
8 8
9 type clipboardImageAttachment struct {9 type clipboardImageAttachment struct {
M packages/agent/modes/clipboard_images_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "strings"4 "strings"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/provider"7+ "github.com/bnema/zut/packages/provider"
8 )8 )
9 9
10 func testClipboardImage(marker string, data string) clipboardImageAttachment {10 func testClipboardImage(marker string, data string) clipboardImageAttachment {
M packages/agent/modes/clipboard_inputs_test.go +1 -1
@@ -3,7 +3,7 @@ package modes
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/tui"6+ "github.com/bnema/zut/packages/tui"
7 )7 )
8 8
9 func TestFilterInputsAcceptPaste(t *testing.T) {9 func TestFilterInputsAcceptPaste(t *testing.T) {
M packages/agent/modes/clipboard_paste.go +1 -1
@@ -3,7 +3,7 @@ package modes
3 import (3 import (
4 "strings"4 "strings"
5 5
6- "github.com/patriceckhart/zot/packages/tui"6+ "github.com/bnema/zut/packages/tui"
7 )7 )
8 8
9 type clipboardTextReader func() (string, bool, error)9 type clipboardTextReader func() (string, bool, error)
M packages/agent/modes/clipboard_paste_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "errors"4 "errors"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/tui"7+ "github.com/bnema/zut/packages/tui"
8 )8 )
9 9
10 func TestResolveClipboardTextConvertsClipboardKeyToPaste(t *testing.T) {10 func TestResolveClipboardTextConvertsClipboardKeyToPaste(t *testing.T) {
M packages/agent/modes/compact_queue_test.go +3 -3
@@ -7,9 +7,9 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12- "github.com/patriceckhart/zot/packages/tui"12+ "github.com/bnema/zut/packages/tui"
13 )13 )
14 14
15 type compactQueueClient struct {15 type compactQueueClient struct {
M packages/agent/modes/confirm_dialog.go +2 -2
@@ -6,8 +6,8 @@ import (
6 "sync"6 "sync"
7 "time"7 "time"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 // confirmRequest is one pending confirmation. The agent goroutine13 // confirmRequest is one pending confirmation. The agent goroutine
M packages/agent/modes/confirm_dialog_test.go +4 -4
@@ -8,10 +8,10 @@ import (
8 "testing"8 "testing"
9 "time"9 "time"
10 10
11- "github.com/patriceckhart/zot/packages/agent/extproto"11+ "github.com/bnema/zut/packages/agent/extproto"
12- "github.com/patriceckhart/zot/packages/agent/tools"12+ "github.com/bnema/zut/packages/agent/tools"
13- "github.com/patriceckhart/zot/packages/core"13+ "github.com/bnema/zut/packages/core"
14- "github.com/patriceckhart/zot/packages/tui"14+ "github.com/bnema/zut/packages/tui"
15 )15 )
16 16
17 func TestConfirmToolCallAttachesDiffBeforeDecision(t *testing.T) {17 func TestConfirmToolCallAttachesDiffBeforeDecision(t *testing.T) {
M packages/agent/modes/dialog_frame.go +1 -1
@@ -5,7 +5,7 @@ import (
5 5
6 "github.com/mattn/go-runewidth"6 "github.com/mattn/go-runewidth"
7 7
8- "github.com/patriceckhart/zot/packages/tui"8+ "github.com/bnema/zut/packages/tui"
9 )9 )
10 10
11 // frameHeader returns a full-width rule with a small title at the left.11 // 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
3 import (3 import (
4 "strings"4 "strings"
5 5
6- "github.com/patriceckhart/zot/packages/tui"6+ "github.com/bnema/zut/packages/tui"
7 )7 )
8 8
9 const dialogTextIndent = " "9 const dialogTextIndent = " "
M packages/agent/modes/dialog_text_test.go +2 -2
@@ -6,8 +6,8 @@ import (
6 6
7 "github.com/mattn/go-runewidth"7 "github.com/mattn/go-runewidth"
8 8
9- "github.com/patriceckhart/zot/packages/agent/subagents"9+ "github.com/bnema/zut/packages/agent/subagents"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 func assertRowsFitWidth(t *testing.T, rows []string, width int) {13 func assertRowsFitWidth(t *testing.T, rows []string, width int) {
M packages/agent/modes/double_escape.go +3 -3
@@ -5,9 +5,9 @@ import (
5 "strings"5 "strings"
6 "time"6 "time"
7 7
8- "github.com/patriceckhart/zot/packages/core"8+ "github.com/bnema/zut/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 const sessionTreeEscapeWindow = 500 * time.Millisecond13 const sessionTreeEscapeWindow = 500 * time.Millisecond
M packages/agent/modes/double_escape_test.go +3 -3
@@ -5,9 +5,9 @@ import (
5 "testing"5 "testing"
6 "time"6 "time"
7 7
8- "github.com/patriceckhart/zot/packages/core"8+ "github.com/bnema/zut/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 func TestDoubleEscapeTrackerWindowAndReset(t *testing.T) {13 func TestDoubleEscapeTrackerWindowAndReset(t *testing.T) {
M packages/agent/modes/ext_notes_test.go +1 -1
@@ -6,7 +6,7 @@ import (
6 "strings"6 "strings"
7 "testing"7 "testing"
8 8
9- "github.com/patriceckhart/zot/packages/tui"9+ "github.com/bnema/zut/packages/tui"
10 )10 )
11 11
12 func newNotesTestInteractive() *Interactive {12 func newNotesTestInteractive() *Interactive {
M packages/agent/modes/ext_panel_dialog.go +1 -1
@@ -5,7 +5,7 @@ import (
5 5
6 "github.com/mattn/go-runewidth"6 "github.com/mattn/go-runewidth"
7 7
8- "github.com/patriceckhart/zot/packages/tui"8+ "github.com/bnema/zut/packages/tui"
9 )9 )
10 10
11 type extPanelDialog struct {11 type extPanelDialog struct {
M packages/agent/modes/fast_mode_test.go +2 -2
@@ -6,8 +6,8 @@ import (
6 "strings"6 "strings"
7 "testing"7 "testing"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 type recordingFastModeSettingsStore struct {13 type recordingFastModeSettingsStore struct {
M packages/agent/modes/file_suggest.go +4 -4
@@ -10,8 +10,8 @@ import (
10 10
11 "github.com/sahilm/fuzzy"11 "github.com/sahilm/fuzzy"
12 12
13- "github.com/patriceckhart/zot/packages/ignore"13+ "github.com/bnema/zut/packages/ignore"
14- "github.com/patriceckhart/zot/packages/tui"14+ "github.com/bnema/zut/packages/tui"
15 )15 )
16 16
17 // recursiveScanLimits bound the recursive walk so the picker stays17 // recursiveScanLimits bound the recursive walk so the picker stays
@@ -63,7 +63,7 @@ type fileSuggester struct {
63 // cachedMTime is the mtime of cachedDir at the time of the scan.63 // cachedMTime is the mtime of cachedDir at the time of the scan.
64 // scan() compares the current mtime against this on every call and64 // scan() compares the current mtime against this on every call and
65 // re-reads the directory if it has changed, so files or folders65 // 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.
67 // Stat is cheap (single syscall) so doing it per keystroke while67 // Stat is cheap (single syscall) so doing it per keystroke while
68 // the popup is open does not impact responsiveness.68 // the popup is open does not impact responsiveness.
69 cachedMTime time.Time69 cachedMTime time.Time
@@ -129,7 +129,7 @@ func (s *fileSuggester) browseDir() string {
129 // Results are cached by absolute path + mtime: a repeated call against129 // Results are cached by absolute path + mtime: a repeated call against
130 // the same directory returns the cached slice when nothing has changed130 // the same directory returns the cached slice when nothing has changed
131 // on disk, and re-reads when an entry was added, removed, or renamed131 // 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 zot132+// (any of which bumps the directory's mtime on every filesystem zut
133 // supports). A failed stat falls through to a fresh ReadDir rather133 // supports). A failed stat falls through to a fresh ReadDir rather
134 // than returning a stale cache so transient errors self-heal.134 // than returning a stale cache so transient errors self-heal.
135 func (s *fileSuggester) scan() []fileEntry {135 func (s *fileSuggester) scan() []fileEntry {
M packages/agent/modes/help.go +2 -2
@@ -4,8 +4,8 @@ import (
4 "fmt"4 "fmt"
5 "strings"5 "strings"
6 6
7+ "github.com/bnema/zut/packages/tui"
7 "github.com/mattn/go-runewidth"8 "github.com/mattn/go-runewidth"
8- "github.com/patriceckhart/zot/packages/tui"
9 )9 )
10 10
11 // helpKeyRows is the list of keybindings shown by /help.11 // helpKeyRows is the list of keybindings shown by /help.
@@ -73,7 +73,7 @@ func renderHelpBlock(th tui.Theme, width int, llamaConfigured bool) []string {
73 }73 }
74 74
75 var out []string75 var out []string
76- out = append(out, frameHeader(th, "zot help", width), "")76+ out = append(out, frameHeader(th, "zut help", width), "")
77 77
78 // commands section78 // commands section
79 out = append(out, tui.Bold("slash commands:"))79 out = append(out, tui.Bold("slash commands:"))
M packages/agent/modes/help_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "strings"4 "strings"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/tui"7+ "github.com/bnema/zut/packages/tui"
8 )8 )
9 9
10 func TestHelpShowsLlamaOnlyWhenConfigured(t *testing.T) {10 func TestHelpShowsLlamaOnlyWhenConfigured(t *testing.T) {
M packages/agent/modes/input_history_test.go +3 -3
@@ -3,9 +3,9 @@ package modes
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/core"6+ "github.com/bnema/zut/packages/core"
7- "github.com/patriceckhart/zot/packages/provider"7+ "github.com/bnema/zut/packages/provider"
8- "github.com/patriceckhart/zot/packages/tui"8+ "github.com/bnema/zut/packages/tui"
9 )9 )
10 10
11 func TestInputHistoryUsesUpDown(t *testing.T) {11 func TestInputHistoryUsesUpDown(t *testing.T) {
M packages/agent/modes/interactive.go +56 -56
@@ -13,16 +13,16 @@ import (
13 "sync"13 "sync"
14 "time"14 "time"
15 15
16- "github.com/patriceckhart/zot/packages/agent/extensions"16+ "github.com/bnema/zut/packages/agent/extensions"
17- "github.com/patriceckhart/zot/packages/agent/extproto"17+ "github.com/bnema/zut/packages/agent/extproto"
18- "github.com/patriceckhart/zot/packages/agent/modes/telegram"18+ "github.com/bnema/zut/packages/agent/modes/telegram"
19- "github.com/patriceckhart/zot/packages/agent/skills"19+ "github.com/bnema/zut/packages/agent/skills"
20- "github.com/patriceckhart/zot/packages/agent/subagents"20+ "github.com/bnema/zut/packages/agent/subagents"
21- "github.com/patriceckhart/zot/packages/agent/tools"21+ "github.com/bnema/zut/packages/agent/tools"
22- "github.com/patriceckhart/zot/packages/core"22+ "github.com/bnema/zut/packages/core"
23- "github.com/patriceckhart/zot/packages/provider"23+ "github.com/bnema/zut/packages/provider"
24- "github.com/patriceckhart/zot/packages/provider/auth"24+ "github.com/bnema/zut/packages/provider/auth"
25- "github.com/patriceckhart/zot/packages/tui"25+ "github.com/bnema/zut/packages/tui"
26 )26 )
27 27
28 // InteractiveConfig configures the interactive loop.28 // InteractiveConfig configures the interactive loop.
@@ -121,12 +121,12 @@ type InteractiveConfig struct {
121 121
122 // FlatTools renders tool calls without the bordered panel (a quiet122 // FlatTools renders tool calls without the bordered panel (a quiet
123 // header line plus indented, frameless output). Mirrors the123 // 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.
125 FlatTools bool125 FlatTools bool
126 126
127 // CompactUser renders sent user messages as a single quiet gutter127 // CompactUser renders sent user messages as a single quiet gutter
128 // line instead of a padded, tinted bubble. Mirrors the resolved128 // 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.
130 CompactUser bool130 CompactUser bool
131 131
132 // CompactMode mirrors the persisted compact_mode flag at startup.132 // CompactMode mirrors the persisted compact_mode flag at startup.
@@ -163,7 +163,7 @@ type InteractiveConfig struct {
163 SubagentsSystemAddendum string163 SubagentsSystemAddendum string
164 SettingsStore SettingsStore164 SettingsStore SettingsStore
165 165
166- // Agent is optional. If nil, zot opens without credentials; the166+ // Agent is optional. If nil, zut opens without credentials; the
167 // user must /login before they can prompt.167 // user must /login before they can prompt.
168 Agent *core.Agent168 Agent *core.Agent
169 169
@@ -209,8 +209,8 @@ type InteractiveConfig struct {
209 // the concrete provider/model in use.209 // the concrete provider/model in use.
210 BuildAgent func() (*core.Agent, string, string, error)210 BuildAgent func() (*core.Agent, string, string, error)
211 211
212- // SetKimiCLIFallbackDisabled controls whether zot may fall back to212+ // SetKimiCLIFallbackDisabled controls whether zut may fall back to
213- // the official Kimi Code CLI token when zot has no stored Kimi token.213+ // the official Kimi Code CLI token when zut has no stored Kimi token.
214 SetKimiCLIFallbackDisabled func(disabled bool) error214 SetKimiCLIFallbackDisabled func(disabled bool) error
215 215
216 // BuildAgentFor rebuilds the agent with an explicit provider/model216 // BuildAgentFor rebuilds the agent with an explicit provider/model
@@ -232,13 +232,13 @@ type InteractiveConfig struct {
232 // picker to only show reachable models.232 // picker to only show reachable models.
233 LoggedInProviders func() []string233 LoggedInProviders func() []string
234 234
235- // ZotHome is zot's global state directory, used by authentication,235+ // ZutHome is zut's global state directory, used by authentication,
236 // themes, extensions, and other shared configuration.236 // themes, extensions, and other shared configuration.
237- ZotHome string237+ ZutHome string
238 238
239 // SessionsRoot is the root passed to core session operations. It differs239 // SessionsRoot is the root passed to core session operations. It differs
240- // from ZotHome for Zotfile agents, whose sessions are isolated by agent240+ // from ZutHome for Zutfile agents, whose sessions are isolated by agent
241- // name. Empty falls back to ZotHome for embedders and tests.241+ // name. Empty falls back to ZutHome for embedders and tests.
242 SessionsRoot string242 SessionsRoot string
243 243
244 // Version is the binary's current version (from main.version).244 // Version is the binary's current version (from main.version).
@@ -260,7 +260,7 @@ type InteractiveConfig struct {
260 // callback returns the new agent message slice so the TUI can invalidate.260 // callback returns the new agent message slice so the TUI can invalidate.
261 LoadSession func(path string) error261 LoadSession func(path string) error
262 262
263- // ChangeCWD switches the running zot session's working directory263+ // ChangeCWD switches the running zut session's working directory
264 // to path. The host closes the current session, rebuilds the264 // to path. The host closes the current session, rebuilds the
265 // agent so tools / AGENTS.md / sandbox bind to the new cwd, and265 // agent so tools / AGENTS.md / sandbox bind to the new cwd, and
266 // opens a fresh session there. Returns an error if path doesn't266 // opens a fresh session there. Returns an error if path doesn't
@@ -690,7 +690,7 @@ type Interactive struct {
690 // shell escape, updated via BashTool progress for live rendering.690 // shell escape, updated via BashTool progress for live rendering.
691 shellLive string691 shellLive string
692 692
693- // awaitingStartupPre is true while the zotfile entry.pre auto-submit693+ // awaitingStartupPre is true while the zutfile entry.pre auto-submit
694 // is in flight. When it clears, deferredInitialInput is applied.694 // is in flight. When it clears, deferredInitialInput is applied.
695 awaitingStartupPre bool695 awaitingStartupPre bool
696 deferredInitialInput string696 deferredInitialInput string
@@ -870,7 +870,7 @@ func (i *Interactive) Run(ctx context.Context) error {
870 // selection over the wheel-speed boost, so we no longer turn it870 // selection over the wheel-speed boost, so we no longer turn it
871 // on automatically. Wheel events fall through to the terminal's871 // on automatically. Wheel events fall through to the terminal's
872 // own scrollback handler.872 // own scrollback handler.
873- // Keep zot on the terminal's main screen. We intentionally do not873+ // Keep zut on the terminal's main screen. We intentionally do not
874 // enter the alternate-screen buffer (CSI ?1049h). The renderer emits874 // enter the alternate-screen buffer (CSI ?1049h). The renderer emits
875 // chat as normal terminal flow/scrollback and redraws only the live875 // chat as normal terminal flow/scrollback and redraws only the live
876 // input/status block on normal typing.876 // input/status block on normal typing.
@@ -939,7 +939,7 @@ func (i *Interactive) Run(ctx context.Context) error {
939 // want to dismiss it (e.g. to check /help or /exit first).939 // want to dismiss it (e.g. to check /help or /exit first).
940 if i.agent == nil {940 if i.agent == nil {
941 i.statusErr = "not logged in. pick a login method below or press esc to dismiss."941 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)
943 }943 }
944 944
945 // Input goroutine. Buffered generously so a drag-drop that the945 // Input goroutine. Buffered generously so a drag-drop that the
@@ -1441,7 +1441,7 @@ func (i *Interactive) buildChatLocked(cols int) []string {
1441 }1441 }
1442 1442
1443 // Update-available banner: prepended above everything else so it's1443 // 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.
1445 // Once rendered, it stays until the user updates to a newer1445 // Once rendered, it stays until the user updates to a newer
1446 // version — we don't persist a "dismissed" flag because this is1446 // version — we don't persist a "dismissed" flag because this is
1447 // cheap and re-showing it is how most users remember to update.1447 // cheap and re-showing it is how most users remember to update.
@@ -1515,7 +1515,7 @@ func (i *Interactive) sessionsRoot() string {
1515 if i.cfg.SessionsRoot != "" {1515 if i.cfg.SessionsRoot != "" {
1516 return i.cfg.SessionsRoot1516 return i.cfg.SessionsRoot
1517 }1517 }
1518- return i.cfg.ZotHome1518+ return i.cfg.ZutHome
1519 }1519 }
1520 1520
1521 // lastCols returns the current terminal width in columns.1521 // lastCols returns the current terminal width in columns.
@@ -1765,7 +1765,7 @@ func (i *Interactive) redraw() {
1765 }1765 }
1766 1766
1767 // Busy prefix shown at the far left of the status bar. The1767 // 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`
1769 // label colour (Theme.Assistant) so the whole "who's working"1769 // label colour (Theme.Assistant) so the whole "who's working"
1770 // band reads at a glance. Elapsed time stays muted because it1770 // band reads at a glance. Elapsed time stays muted because it
1771 // drifts every second and shouldn't grab focus.1771 // drifts every second and shouldn't grab focus.
@@ -2149,7 +2149,7 @@ func hasImageEscape(line string) bool {
2149 2149
2150 // snapViewportStartToImageBlock treats inline images as atomic blocks for2150 // snapViewportStartToImageBlock treats inline images as atomic blocks for
2151 // scrolling. Terminal image protocols draw from a single escape row into a2151 // scrolling. Terminal image protocols draw from a single escape row into a
2152-// separate graphics layer; the following blank rows are only zot's reserved2152+// separate graphics layer; the following blank rows are only zut's reserved
2153 // footprint. If the viewport starts on one of those blank rows, there is no2153 // footprint. If the viewport starts on one of those blank rows, there is no
2154 // correct partial-image state to render. Snap back to the escape row instead2154 // correct partial-image state to render. Snap back to the escape row instead
2155 // so the image is either shown from its beginning or skipped entirely.2155 // 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) {
2857 // ("be quiet" in a shell) rather than a deliberate2857 // ("be quiet" in a shell) rather than a deliberate
2858 // decision to kill a multi-minute model call that's2858 // decision to kill a multi-minute model call that's
2859 // already cost tokens. Use esc to interrupt a turn; use2859 // already cost tokens. Use esc to interrupt a turn; use
2860- // a deliberate double-ctrl+c to exit zot entirely. First2860+ // a deliberate double-ctrl+c to exit zut entirely. First
2861 // press arms the exit hint, second press within2861 // press arms the exit hint, second press within
2862 // ctrlCExitWindow quits.2862 // ctrlCExitWindow quits.
2863 if i.busy {2863 if i.busy {
@@ -4094,7 +4094,7 @@ func (i *Interactive) openSettingsDialog() {
4094 if themeName == "" {4094 if themeName == "" {
4095 themeName = "auto"4095 themeName = "auto"
4096 }4096 }
4097- if themeName != "auto" && !tui.ThemeExists(i.cfg.ZotHome, themeName) {4097+ if themeName != "auto" && !tui.ThemeExists(i.cfg.ZutHome, themeName) {
4098 themeName = "auto"4098 themeName = "auto"
4099 i.cfg.ThemeName = ""4099 i.cfg.ThemeName = ""
4100 if i.cfg.SettingsStore != nil {4100 if i.cfg.SettingsStore != nil {
@@ -4104,7 +4104,7 @@ func (i *Interactive) openSettingsDialog() {
4104 }4104 }
4105 themeOptions := []settingsOption{}4105 themeOptions := []settingsOption{}
4106 themeChoice := 04106 themeChoice := 0
4107- availableThemes := tui.AvailableThemes(i.cfg.ZotHome)4107+ availableThemes := tui.AvailableThemes(i.cfg.ZutHome)
4108 if i.cfg.ExtensionThemes != nil {4108 if i.cfg.ExtensionThemes != nil {
4109 availableThemes = append(availableThemes, i.cfg.ExtensionThemes()...)4109 availableThemes = append(availableThemes, i.cfg.ExtensionThemes()...)
4110 }4110 }
@@ -4210,7 +4210,7 @@ func (i *Interactive) openSettingsDialog() {
4210 {4210 {
4211 key: "auto_compact_threshold",4211 key: "auto_compact_threshold",
4212 label: "auto-compact threshold",4212 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",
4214 options: autoCompactOptions,4214 options: autoCompactOptions,
4215 choice: autoCompactChoice,4215 choice: autoCompactChoice,
4216 },4216 },
@@ -4809,7 +4809,7 @@ func (i *Interactive) applySettingToggle(key string, value bool) {
4809 }4809 }
4810 }4810 }
4811 // Flip the live picker so the next @ reflects the new mode4811 // 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.
4813 i.fileSuggest.SetRecursive(value)4813 i.fileSuggest.SetRecursive(value)
4814 i.mu.Lock()4814 i.mu.Lock()
4815 i.statusOK = "recursive @-file search " + onOff(value)4815 i.statusOK = "recursive @-file search " + onOff(value)
@@ -4982,13 +4982,13 @@ func (i *Interactive) applyThemeNow(name string) {
4982 // themes live; inherited mode must not issue OSC queries while the TUI4982 // themes live; inherited mode must not issue OSC queries while the TUI
4983 // already owns raw stdin.4983 // already owns raw stdin.
4984 detected.Terminal = i.cfg.Theme.Terminal4984 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)
4986 if err != nil {4986 if err != nil {
4987 if i.cfg.SettingsStore != nil {4987 if i.cfg.SettingsStore != nil {
4988 _ = i.cfg.SettingsStore.SetTheme("auto")4988 _ = i.cfg.SettingsStore.SetTheme("auto")
4989 }4989 }
4990 i.cfg.ThemeName = ""4990 i.cfg.ThemeName = ""
4991- th, _, _ = tui.LoadThemeFromHome(i.cfg.ZotHome, "auto", detected)4991+ th, _, _ = tui.LoadThemeFromHome(i.cfg.ZutHome, "auto", detected)
4992 i.mu.Lock()4992 i.mu.Lock()
4993 i.statusErr = "theme missing; reset to default"4993 i.statusErr = "theme missing; reset to default"
4994 i.mu.Unlock()4994 i.mu.Unlock()
@@ -5334,7 +5334,7 @@ func (i *Interactive) runSlash(ctx context.Context, cmd string) (done bool) {
5334 i.scrollOffset = 05334 i.scrollOffset = 0
5335 i.mu.Unlock()5335 i.mu.Unlock()
5336 case "/login":5336 case "/login":
5337- i.dialog.Open(i.cfg.ZotHome)5337+ i.dialog.Open(i.cfg.ZutHome)
5338 case "/logout":5338 case "/logout":
5339 if len(parts) >= 2 {5339 if len(parts) >= 2 {
5340 // Explicit target: /logout anthropic | openai | all5340 // Explicit target: /logout anthropic | openai | all
@@ -5431,7 +5431,7 @@ func (i *Interactive) runSlash(ctx context.Context, cmd string) (done bool) {
5431 // Hidden command: switch the running session's cwd. Not in5431 // Hidden command: switch the running session's cwd. Not in
5432 // slash_suggest, not in /help. Used by the workspaces5432 // slash_suggest, not in /help. Used by the workspaces
5433 // extension's panel-key Enter handler so picking a row5433 // 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.
5435 //5435 //
5436 // Recovers the raw argument (path) from the original cmd5436 // Recovers the raw argument (path) from the original cmd
5437 // string rather than parts, so paths with spaces survive.5437 // string rather than parts, so paths with spaces survive.
@@ -5727,11 +5727,11 @@ func (i *Interactive) doLogout(target string) {
5727 }5727 }
5728 5728
5729 func providerSetupInfo(provider string) (string, []string, bool) {5729 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"
5731 switch provider {5731 switch provider {
5732 case "amazon-bedrock":5732 case "amazon-bedrock":
5733 return "Amazon Bedrock setup", []string{5733 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.",
5735 "Configure an AWS profile, IAM keys, bearer token, or role-based credentials.",5735 "Configure an AWS profile, IAM keys, bearer token, or role-based credentials.",
5736 "",5736 "",
5737 "For Bedrock API keys, set:",5737 "For Bedrock API keys, set:",
@@ -6736,7 +6736,7 @@ func (i *Interactive) startTurnRequest(parent context.Context, prompt string, im
6736 // "last frame had the previous turn's tool overlay" and6736 // "last frame had the previous turn's tool overlay" and
6737 // "this frame had it cleared above". Without this, the guard6737 // "this frame had it cleared above". Without this, the guard
6738 // reads delta = -(rows in cleared overlay) and decrements6738 // reads delta = -(rows in cleared overlay) and decrements
6739- // scrollOffset, which on terminals that mirror zot's pane6739+ // scrollOffset, which on terminals that mirror zut's pane
6740 // scroll into the host scrollbar visibly yanks the viewport.6740 // scroll into the host scrollbar visibly yanks the viewport.
6741 // See autofollow_shrink_test.go for the exact arithmetic.6741 // See autofollow_shrink_test.go for the exact arithmetic.
6742 i.prevChatLen = 06742 i.prevChatLen = 0
@@ -6827,7 +6827,7 @@ func (i *Interactive) startTurnRequest(parent context.Context, prompt string, im
6827 }6827 }
6828 // Persist the assistant's reply (and every tool row before6828 // Persist the assistant's reply (and every tool row before
6829 // it) to the session file while the turn memory is hot.6829 // 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,
6831 // meaning a crash or ungraceful kill drops the whole6831 // meaning a crash or ungraceful kill drops the whole
6832 // conversation. FlushSession is idempotent (it advances the6832 // conversation. FlushSession is idempotent (it advances the
6833 // baseline so subsequent flushes only write new rows).6833 // baseline so subsequent flushes only write new rows).
@@ -7145,7 +7145,7 @@ func (i *Interactive) handleEvent(ev core.AgentEvent) {
7145 if tc, ok := i.toolCalls[e.ID]; ok {7145 if tc, ok := i.toolCalls[e.ID]; ok {
7146 tc.RawJSONBuf += e.Delta7146 tc.RawJSONBuf += e.Delta
7147 // Refresh the live path as soon as it parses; used in7147 // 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)
7149 // while the content is still streaming.7149 // while the content is still streaming.
7150 if p, pok, _ := tui.ExtractPartialStringField(tc.RawJSONBuf, "path"); pok {7150 if p, pok, _ := tui.ExtractPartialStringField(tc.RawJSONBuf, "path"); pok {
7151 tc.LivePath = p7151 tc.LivePath = p
@@ -7365,7 +7365,7 @@ func (i *Interactive) openTelegramDialog() {
7365 items := i.telegramMenuItems()7365 items := i.telegramMenuItems()
7366 if len(items) == 0 {7366 if len(items) == 0 {
7367 i.mu.Lock()7367 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."
7369 i.mu.Unlock()7369 i.mu.Unlock()
7370 i.invalidate()7370 i.invalidate()
7371 return7371 return
@@ -7378,7 +7378,7 @@ func (i *Interactive) openTelegramDialog() {
7378 // bridge state. Returns empty when no bot.json exists so the7378 // bridge state. Returns empty when no bot.json exists so the
7379 // caller can show a helpful status line instead of an empty menu.7379 // caller can show a helpful status line instead of an empty menu.
7380 func (i *Interactive) telegramMenuItems() []telegramItem {7380 func (i *Interactive) telegramMenuItems() []telegramItem {
7381- cfg, err := telegram.LoadConfig(i.cfg.ZotHome)7381+ cfg, err := telegram.LoadConfig(i.cfg.ZutHome)
7382 if err != nil || cfg.BotToken == "" {7382 if err != nil || cfg.BotToken == "" {
7383 return nil7383 return nil
7384 }7384 }
@@ -7432,7 +7432,7 @@ func (i *Interactive) telegramConnect() {
7432 i.invalidate()7432 i.invalidate()
7433 return7433 return
7434 }7434 }
7435- cfg, err := telegram.LoadConfig(i.cfg.ZotHome)7435+ cfg, err := telegram.LoadConfig(i.cfg.ZutHome)
7436 if err != nil {7436 if err != nil {
7437 i.mu.Lock()7437 i.mu.Lock()
7438 i.statusErr = "telegram: " + err.Error()7438 i.statusErr = "telegram: " + err.Error()
@@ -7442,7 +7442,7 @@ func (i *Interactive) telegramConnect() {
7442 }7442 }
7443 if cfg.BotToken == "" {7443 if cfg.BotToken == "" {
7444 i.mu.Lock()7444 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."
7446 i.mu.Unlock()7446 i.mu.Unlock()
7447 i.invalidate()7447 i.invalidate()
7448 return7448 return
@@ -7450,10 +7450,10 @@ func (i *Interactive) telegramConnect() {
7450 // Refuse to start when a background daemon is already polling7450 // Refuse to start when a background daemon is already polling
7451 // the same bot. Two concurrent long-poll consumers race each7451 // the same bot. Two concurrent long-poll consumers race each
7452 // update and one always loses, so DMs get half-delivered. The7452 // update and one always loses, so DMs get half-delivered. The
7453- // user can `zot telegram-bot stop` first, then /telegram connect.7453+ // user can `zut telegram-bot stop` first, then /telegram connect.
7454- if pid, alive, _ := telegram.IsRunning(i.cfg.ZotHome); alive && pid > 0 {7454+ if pid, alive, _ := telegram.IsRunning(i.cfg.ZutHome); alive && pid > 0 {
7455 i.mu.Lock()7455 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)
7457 i.mu.Unlock()7457 i.mu.Unlock()
7458 i.invalidate()7458 i.invalidate()
7459 return7459 return
@@ -7462,7 +7462,7 @@ func (i *Interactive) telegramConnect() {
7462 Client: telegram.NewClient(cfg.BotToken),7462 Client: telegram.NewClient(cfg.BotToken),
7463 Config: cfg,7463 Config: cfg,
7464 Save: func(next telegram.Config) error {7464 Save: func(next telegram.Config) error {
7465- return telegram.SaveConfig(i.cfg.ZotHome, next)7465+ return telegram.SaveConfig(i.cfg.ZutHome, next)
7466 },7466 },
7467 Host: &telegramHost{iv: i},7467 Host: &telegramHost{iv: i},
7468 }7468 }
@@ -7884,12 +7884,12 @@ func (i *Interactive) telegramStatus() {
7884 } else {7884 } else {
7885 msg += " - awaiting pairing"7885 msg += " - awaiting pairing"
7886 }7886 }
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 {
7888 msg = fmt.Sprintf("telegram: background daemon running (pid %d) - /telegram connect won't work until you stop it", pid)7888 msg = fmt.Sprintf("telegram: background daemon running (pid %d) - /telegram connect won't work until you stop it", pid)
7889 } else {7889 } else {
7890- cfg, _ := telegram.LoadConfig(i.cfg.ZotHome)7890+ cfg, _ := telegram.LoadConfig(i.cfg.ZutHome)
7891 if cfg.BotToken == "" {7891 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."
7893 } else {7893 } else {
7894 msg = "telegram: disconnected"7894 msg = "telegram: disconnected"
7895 if cfg.BotUsername != "" {7895 if cfg.BotUsername != "" {
@@ -7963,8 +7963,8 @@ func (h *telegramHost) Notify(level, message string) {
7963 // transcript on fork; no parent/siblings on tree).7963 // transcript on fork; no parent/siblings on tree).
7964 func (i *Interactive) openSessionOpsDialog() {7964 func (i *Interactive) openSessionOpsDialog() {
7965 items := []sessionOpsItem{7965 items := []sessionOpsItem{
7966- {label: "export", action: "export", hint: "write the current session to a .zotsession file"},7966+ {label: "export", action: "export", hint: "write the current session to a .zutsession file"},
7967- {label: "import", action: "import", hint: "load a .zotsession file into this directory"},7967+ {label: "import", action: "import", hint: "load a .zutsession file into this directory"},
7968 {label: "fork", action: "fork", hint: "branch from a past user message into a new session"},7968 {label: "fork", action: "fork", hint: "branch from a past user message into a new session"},
7969 {label: "tree", action: "tree", hint: "switch between branches in this directory"},7969 {label: "tree", action: "tree", hint: "switch between branches in this directory"},
7970 }7970 }
@@ -8034,7 +8034,7 @@ func (i *Interactive) doSessionExport(dst string) {
8034 i.invalidate()8034 i.invalidate()
8035 }8035 }
8036 8036
8037-// doSessionImport copies the .zotsession file at src into the8037+// doSessionImport copies the .zutsession file at src into the
8038 // running cwd's sessions directory and loads it as the active8038 // running cwd's sessions directory and loads it as the active
8039 // session, same as `/sessions` -> pick. When src is empty we ask8039 // session, same as `/sessions` -> pick. When src is empty we ask
8040 // the user to pass a path (no usable default here).8040 // the user to pass a path (no usable default here).
@@ -8042,7 +8042,7 @@ func (i *Interactive) doSessionImport(src string) {
8042 src = unquotePath(src)8042 src = unquotePath(src)
8043 if src == "" {8043 if src == "" {
8044 i.mu.Lock()8044 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"
8046 i.mu.Unlock()8046 i.mu.Unlock()
8047 i.invalidate()8047 i.invalidate()
8048 return8048 return
M packages/agent/modes/interactive_model_test.go +1 -1
@@ -6,7 +6,7 @@ import (
6 "strings"6 "strings"
7 "testing"7 "testing"
8 8
9- "github.com/patriceckhart/zot/packages/tui"9+ "github.com/bnema/zut/packages/tui"
10 )10 )
11 11
12 func TestReasoningSelectorUsesCurrentModelLevels(t *testing.T) {12 func TestReasoningSelectorUsesCurrentModelLevels(t *testing.T) {
M packages/agent/modes/jail_setting_test.go +1 -1
@@ -3,7 +3,7 @@ package modes
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/agent/tools"6+ "github.com/bnema/zut/packages/agent/tools"
7 )7 )
8 8
9 func TestJailByDefaultSettingUpdatesLiveSandbox(t *testing.T) {9 func TestJailByDefaultSettingUpdatesLiveSandbox(t *testing.T) {
M packages/agent/modes/json.go +3 -3
@@ -6,8 +6,8 @@ import (
6 "fmt"6 "fmt"
7 "io"7 "io"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 // RunJSON runs the agent to completion, writing one JSON object per13 // 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
35 35
36 // EventToJSON converts an AgentEvent to a JSON-friendly map. The on-wire36 // EventToJSON converts an AgentEvent to a JSON-friendly map. The on-wire
37 // schema is deliberately simple and flat. Exported so the RPC mode can37 // 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`.
39 func EventToJSON(ev core.AgentEvent) map[string]any {39 func EventToJSON(ev core.AgentEvent) map[string]any {
40 m := map[string]any{"type": ev.Type()}40 m := map[string]any{"type": ev.Type()}
41 switch e := ev.(type) {41 switch e := ev.(type) {
M packages/agent/modes/jump_dialog.go +2 -2
@@ -4,8 +4,8 @@ import (
4 "fmt"4 "fmt"
5 "strings"5 "strings"
6 6
7- "github.com/patriceckhart/zot/packages/provider"7+ "github.com/bnema/zut/packages/provider"
8- "github.com/patriceckhart/zot/packages/tui"8+ "github.com/bnema/zut/packages/tui"
9 )9 )
10 10
11 // jumpTarget describes one "turn" in the current session — a user11 // jumpTarget describes one "turn" in the current session — a user
M packages/agent/modes/llama_dialog.go +2 -2
@@ -8,9 +8,9 @@ import (
8 "sync"8 "sync"
9 "time"9 "time"
10 10
11+ "github.com/bnema/zut/packages/provider"
12+ "github.com/bnema/zut/packages/tui"
11 "github.com/mattn/go-runewidth"13 "github.com/mattn/go-runewidth"
12- "github.com/patriceckhart/zot/packages/provider"
13- "github.com/patriceckhart/zot/packages/tui"
14 )14 )
15 15
16 type llamaDialogStep int16 type llamaDialogStep int
M packages/agent/modes/llama_dialog_test.go +2 -2
@@ -8,8 +8,8 @@ import (
8 "testing"8 "testing"
9 "time"9 "time"
10 10
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12- "github.com/patriceckhart/zot/packages/tui"12+ "github.com/bnema/zut/packages/tui"
13 )13 )
14 14
15 func TestLlamaDialogSearchesAsUserTypes(t *testing.T) {15 func TestLlamaDialogSearchesAsUserTypes(t *testing.T) {
M packages/agent/modes/login_dialog.go +6 -6
@@ -6,9 +6,9 @@ import (
6 "sort"6 "sort"
7 "strings"7 "strings"
8 8
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10- "github.com/patriceckhart/zot/packages/provider/auth"10+ "github.com/bnema/zut/packages/provider/auth"
11- "github.com/patriceckhart/zot/packages/tui"11+ "github.com/bnema/zut/packages/tui"
12 )12 )
13 13
14 // loginStep is the current node in the login dialog state machine.14 // 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
66 66
67 // Open starts the dialog from scratch and captures the current67 // Open starts the dialog from scratch and captures the current
68 // login status for each provider so the picker can show it.68 // login status for each provider so the picker can show it.
69-// zotHome is the zot state directory ($ZOT_HOME); auth.json69+// zutHome is the zut state directory ($ZUT_HOME); auth.json
70 // lives inside it. Passing the path in (instead of importing70 // lives inside it. Passing the path in (instead of importing
71 // the agent package to call AuthPath()) avoids a cyclic import71 // the agent package to call AuthPath()) avoids a cyclic import
72 // between agent and agent/modes.72 // between agent and agent/modes.
73-func (d *loginDialog) Open(zotHome string) {73+func (d *loginDialog) Open(zutHome string) {
74 d.step = loginStepMethod74 d.step = loginStepMethod
75 d.method = ""75 d.method = ""
76 d.provider = ""76 d.provider = ""
@@ -94,7 +94,7 @@ func (d *loginDialog) Open(zotHome string) {
94 // provider as not-logged-in. The status line just won't show94 // provider as not-logged-in. The status line just won't show
95 // anything useful in that case, which is fine — the user95 // anything useful in that case, which is fine — the user
96 // was about to log in anyway.96 // was about to log in anyway.
97- path := filepath.Join(zotHome, "auth.json")97+ path := filepath.Join(zutHome, "auth.json")
98 if creds, err := auth.NewStore(path).Load(); err == nil {98 if creds, err := auth.NewStore(path).Load(); err == nil {
99 d.status["anthropic"] = creds.Method("anthropic")99 d.status["anthropic"] = creds.Method("anthropic")
100 d.status["openai"] = ""100 d.status["openai"] = ""
M packages/agent/modes/login_dialog_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "strings"4 "strings"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/tui"7+ "github.com/bnema/zut/packages/tui"
8 )8 )
9 9
10 func TestLoginDialogProviderPickerFiltersLikeModelPicker(t *testing.T) {10 func TestLoginDialogProviderPickerFiltersLikeModelPicker(t *testing.T) {
M packages/agent/modes/logout_dialog.go +1 -1
@@ -1,7 +1,7 @@
1 package modes1 package modes
2 2
3 import (3 import (
4- "github.com/patriceckhart/zot/packages/tui"4+ "github.com/bnema/zut/packages/tui"
5 )5 )
6 6
7 // logoutDialog is the picker shown when the user runs `/logout`7 // 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 (
4 "strings"4 "strings"
5 "testing"5 "testing"
6 6
7+ "github.com/bnema/zut/packages/tui"
7 "github.com/mattn/go-runewidth"8 "github.com/mattn/go-runewidth"
8- "github.com/patriceckhart/zot/packages/tui"
9 )9 )
10 10
11 func TestLogoutDialogSelectionBackgroundSpansFullWidth(t *testing.T) {11 func TestLogoutDialogSelectionBackgroundSpansFullWidth(t *testing.T) {
M packages/agent/modes/model_dialog.go +2 -2
@@ -5,9 +5,9 @@ import (
5 "sort"5 "sort"
6 "strings"6 "strings"
7 7
8+ "github.com/bnema/zut/packages/provider"
9+ "github.com/bnema/zut/packages/tui"
8 "github.com/mattn/go-runewidth"10 "github.com/mattn/go-runewidth"
9- "github.com/patriceckhart/zot/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"
11 )11 )
12 12
13 // modelDialog is an inline picker for choosing the active model.13 // 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 (
5 "strings"5 "strings"
6 "testing"6 "testing"
7 7
8- "github.com/patriceckhart/zot/packages/core"8+ "github.com/bnema/zut/packages/core"
9 )9 )
10 10
11 type recordingPonytailSettingsStore struct {11 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.
2 package modes2 package modes
3 3
4 import (4 import (
@@ -7,8 +7,8 @@ import (
7 "io"7 "io"
8 "strings"8 "strings"
9 9
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12 )12 )
13 13
14 // RunPrint runs the agent to completion and writes only the final14 // 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 (
5 "context"5 "context"
6 "testing"6 "testing"
7 7
8- "github.com/patriceckhart/zot/packages/core"8+ "github.com/bnema/zut/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10 )10 )
11 11
12 type printTestClient struct{}12 type printTestClient struct{}
M packages/agent/modes/reload_ext.go +2 -2
@@ -6,8 +6,8 @@ import (
6 "strings"6 "strings"
7 "time"7 "time"
8 8
9- "github.com/patriceckhart/zot/packages/agent/extensions"9+ "github.com/bnema/zut/packages/agent/extensions"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 const reloadStatusDuration = 5 * time.Second13 const reloadStatusDuration = 5 * time.Second
M packages/agent/modes/reload_ext_test.go +3 -3
@@ -5,9 +5,9 @@ import (
5 "strings"5 "strings"
6 "testing"6 "testing"
7 7
8- "github.com/patriceckhart/zot/packages/agent/extensions"8+ "github.com/bnema/zut/packages/agent/extensions"
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 func TestFormatReloadStatusIncludesErrorDetails(t *testing.T) {13 func TestFormatReloadStatusIncludesErrorDetails(t *testing.T) {
M packages/agent/modes/rescue_dialog.go +2 -2
@@ -4,8 +4,8 @@ import (
4 "fmt"4 "fmt"
5 "strings"5 "strings"
6 6
7- "github.com/patriceckhart/zot/packages/provider"7+ "github.com/bnema/zut/packages/provider"
8- "github.com/patriceckhart/zot/packages/tui"8+ "github.com/bnema/zut/packages/tui"
9 )9 )
10 10
11 // rescueDialog offers a quick model swap when the active turn fails11 // 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 (
4 "context"4 "context"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/core"7+ "github.com/bnema/zut/packages/core"
8- "github.com/patriceckhart/zot/packages/provider"8+ "github.com/bnema/zut/packages/provider"
9 )9 )
10 10
11 func TestSessionsSlashUsesAgentSessionRoot(t *testing.T) {11 func TestSessionsSlashUsesAgentSessionRoot(t *testing.T) {
12- zotHome := t.TempDir()12+ zutHome := t.TempDir()
13 agentRoot := t.TempDir()13 agentRoot := t.TempDir()
14 cwd := t.TempDir()14 cwd := t.TempDir()
15 15
@@ -28,7 +28,7 @@ func TestSessionsSlashUsesAgentSessionRoot(t *testing.T) {
28 }28 }
29 29
30 i := NewInteractive(InteractiveConfig{30 i := NewInteractive(InteractiveConfig{
31- ZotHome: zotHome,31+ ZutHome: zutHome,
32 SessionsRoot: agentRoot,32 SessionsRoot: agentRoot,
33 CWD: cwd,33 CWD: cwd,
34 })34 })
@@ -46,9 +46,9 @@ func TestSessionsSlashUsesAgentSessionRoot(t *testing.T) {
46 }46 }
47 }47 }
48 48
49-func TestSessionsRootDefaultsToZotHome(t *testing.T) {49+func TestSessionsRootDefaultsToZutHome(t *testing.T) {
50- i := &Interactive{cfg: InteractiveConfig{ZotHome: "/zot/home"}}50+ i := &Interactive{cfg: InteractiveConfig{ZutHome: "/zut/home"}}
51- if got := i.sessionsRoot(); got != "/zot/home" {51+ if got := i.sessionsRoot(); got != "/zut/home" {
52- t.Fatalf("sessions root = %q, want ZotHome fallback", got)52+ t.Fatalf("sessions root = %q, want ZutHome fallback", got)
53 }53 }
54 }54 }
M packages/agent/modes/session_dialog.go +2 -2
@@ -7,8 +7,8 @@ import (
7 "sync"7 "sync"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11- "github.com/patriceckhart/zot/packages/tui"11+ "github.com/bnema/zut/packages/tui"
12 )12 )
13 13
14 type sessionLoadEventKind uint814 type sessionLoadEventKind uint8
M packages/agent/modes/session_dialog_test.go +3 -3
@@ -8,9 +8,9 @@ import (
8 "time"8 "time"
9 "unicode"9 "unicode"
10 10
11- "github.com/patriceckhart/zot/packages/core"11+ "github.com/bnema/zut/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"12+ "github.com/bnema/zut/packages/provider"
13- "github.com/patriceckhart/zot/packages/tui"13+ "github.com/bnema/zut/packages/tui"
14 )14 )
15 15
16 func TestFormatSessionRowPlainSanitizesControlBytes(t *testing.T) {16 func TestFormatSessionRowPlainSanitizesControlBytes(t *testing.T) {
M packages/agent/modes/session_ops_dialog.go +2 -2
@@ -1,13 +1,13 @@
1 package modes1 package modes
2 2
3 import (3 import (
4- "github.com/patriceckhart/zot/packages/tui"4+ "github.com/bnema/zut/packages/tui"
5 )5 )
6 6
7 // sessionOpsDialog is the picker shown when the user runs `/session`7 // sessionOpsDialog is the picker shown when the user runs `/session`
8 // without an argument. Offers the two portable-file operations on8 // without an argument. Offers the two portable-file operations on
9 // the current conversation: export (write the in-memory transcript9 // the current conversation: export (write the in-memory transcript
10-// plus meta to a .zotsession file) and import (load a .zotsession10+// plus meta to a .zutsession file) and import (load a .zutsession
11 // from another machine and swap it in as the active session).11 // from another machine and swap it in as the active session).
12 //12 //
13 // Shape mirrors telegramDialog and logoutDialog: tiny list, arrow13 // Shape mirrors telegramDialog and logoutDialog: tiny list, arrow
M packages/agent/modes/session_title.go +4 -4
@@ -5,9 +5,9 @@ import (
5 "strings"5 "strings"
6 "time"6 "time"
7 7
8- "github.com/patriceckhart/zot/packages/core"8+ "github.com/bnema/zut/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 const sessionTitleGenerationTimeout = 10 * time.Second13 const sessionTitleGenerationTimeout = 10 * time.Second
@@ -126,7 +126,7 @@ func (i *Interactive) writeTerminalTitleLocked(subject string) {
126 if subject != "" && !terminalTitleEnabled(i.cfg.TerminalTitleEnabled) {126 if subject != "" && !terminalTitleEnabled(i.cfg.TerminalTitleEnabled) {
127 return127 return
128 }128 }
129- display := "zot"129+ display := "zut"
130 if subject != "" {130 if subject != "" {
131 display += ": " + subject131 display += ": " + subject
132 }132 }
M packages/agent/modes/session_title_test.go +5 -5
@@ -8,8 +8,8 @@ import (
8 "testing"8 "testing"
9 "time"9 "time"
10 10
11- "github.com/patriceckhart/zot/packages/core"11+ "github.com/bnema/zut/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"12+ "github.com/bnema/zut/packages/provider"
13 )13 )
14 14
15 type interactiveTitleClient struct {15 type interactiveTitleClient struct {
@@ -119,7 +119,7 @@ func TestFirstRealPromptSetsAndPersistsTerminalTitle(t *testing.T) {
119 case <-time.After(time.Second):119 case <-time.After(time.Second):
120 t.Fatal("title was not persisted")120 t.Fatal("title was not persisted")
121 }121 }
122- waitTerminalOutput(t, term, "\x1b]0;zot: Fix login retries\x07")122+ waitTerminalOutput(t, term, "\x1b]0;zut: Fix login retries\x07")
123 if got := len(ag.Messages()); got != 2 {123 if got := len(ag.Messages()); got != 2 {
124 t.Fatalf("transcript message count = %d, want user + assistant only", got)124 t.Fatalf("transcript message count = %d, want user + assistant only", got)
125 }125 }
@@ -264,7 +264,7 @@ func TestDisablingTerminalTitlesRestoresNeutralTitle(t *testing.T) {
264 i.applySettingToggle("terminal_title_enabled", false)264 i.applySettingToggle("terminal_title_enabled", false)
265 265
266 got := term.String()266 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") {
268 t.Fatalf("terminal title output = %q, want existing and neutral title sequences", got)268 t.Fatalf("terminal title output = %q, want existing and neutral title sequences", got)
269 }269 }
270 }270 }
@@ -283,7 +283,7 @@ func TestLoadedSessionTitleIsRestoredWithoutGeneration(t *testing.T) {
283 })283 })
284 i.markInteractiveStarted()284 i.markInteractiveStarted()
285 285
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 {
287 t.Fatalf("restored terminal title = %q, want %q", got, want)287 t.Fatalf("restored terminal title = %q, want %q", got, want)
288 }288 }
289 i.mu.Lock()289 i.mu.Lock()
M packages/agent/modes/session_tree_dialog.go +5 -5
@@ -8,9 +8,9 @@ import (
8 8
9 "github.com/mattn/go-runewidth"9 "github.com/mattn/go-runewidth"
10 10
11- "github.com/patriceckhart/zot/packages/core"11+ "github.com/bnema/zut/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"12+ "github.com/bnema/zut/packages/provider"
13- "github.com/patriceckhart/zot/packages/tui"13+ "github.com/bnema/zut/packages/tui"
14 )14 )
15 15
16 // sessionTreeBoundary describes what a tree row points at. Message rows are16 // sessionTreeBoundary describes what a tree row points at. Message rows are
@@ -738,7 +738,7 @@ func sessionTreeRoleLabel(role provider.Role) string {
738 case provider.RoleUser:738 case provider.RoleUser:
739 return "you"739 return "you"
740 case provider.RoleAssistant:740 case provider.RoleAssistant:
741- return "zot"741+ return "zut"
742 case provider.RoleTool:742 case provider.RoleTool:
743 return "tool"743 return "tool"
744 default:744 default:
@@ -822,7 +822,7 @@ func colorSessionTreeLine(th tui.Theme, line string) string {
822 switch {822 switch {
823 case strings.HasPrefix(plain, "you:"):823 case strings.HasPrefix(plain, "you:"):
824 return th.FGColor(th.FG, line)824 return th.FGColor(th.FG, line)
825- case strings.HasPrefix(plain, "zot:"):825+ case strings.HasPrefix(plain, "zut:"):
826 return th.FGColor(th.Muted, line)826 return th.FGColor(th.Muted, line)
827 case strings.HasPrefix(plain, "tool:"):827 case strings.HasPrefix(plain, "tool:"):
828 return th.FGColor(th.ToolOut, line)828 return th.FGColor(th.ToolOut, line)
M packages/agent/modes/session_tree_dialog_test.go +3 -3
@@ -6,9 +6,9 @@ import (
6 "testing"6 "testing"
7 "time"7 "time"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11- "github.com/patriceckhart/zot/packages/tui"11+ "github.com/bnema/zut/packages/tui"
12 )12 )
13 13
14 func TestSessionTreeTargetCarriesEffectiveBoundaryAndDraft(t *testing.T) {14 func TestSessionTreeTargetCarriesEffectiveBoundaryAndDraft(t *testing.T) {
M packages/agent/modes/settings_dialog.go +1 -1
@@ -5,7 +5,7 @@ import (
5 5
6 "github.com/mattn/go-runewidth"6 "github.com/mattn/go-runewidth"
7 7
8- "github.com/patriceckhart/zot/packages/tui"8+ "github.com/bnema/zut/packages/tui"
9 )9 )
10 10
11 type settingsDialog struct {11 type settingsDialog struct {
M packages/agent/modes/shell_escape_test.go +4 -4
@@ -7,15 +7,15 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11 )11 )
12 12
13 func TestShellEscapeAddsOutputToAgentContext(t *testing.T) {13 func TestShellEscapeAddsOutputToAgentContext(t *testing.T) {
14 agent := core.NewAgent(nil, "", "", nil)14 agent := core.NewAgent(nil, "", "", nil)
15 i := NewInteractive(InteractiveConfig{Agent: agent, CWD: t.TempDir()})15 i := NewInteractive(InteractiveConfig{Agent: agent, CWD: t.TempDir()})
16- cmd := "printf zot-shell-context"16+ cmd := "printf zut-shell-context"
17 if runtime.GOOS == "windows" {17 if runtime.GOOS == "windows" {
18- cmd = "echo zot-shell-context"18+ cmd = "echo zut-shell-context"
19 }19 }
20 20
21 i.startShellEscape(context.Background(), cmd)21 i.startShellEscape(context.Background(), cmd)
@@ -28,7 +28,7 @@ func TestShellEscapeAddsOutputToAgentContext(t *testing.T) {
28 i.mu.Unlock()28 i.mu.Unlock()
29 if len(messages) == 1 && !running {29 if len(messages) == 1 && !running {
30 text := userMessageText(messages[0])30 text := userMessageText(messages[0])
31- if !strings.Contains(text, "zot-shell-context") {31+ if !strings.Contains(text, "zut-shell-context") {
32 t.Fatalf("shell context = %q, want command output", text)32 t.Fatalf("shell context = %q, want command output", text)
33 }33 }
34 if messages[0].Meta[shellEscapeMetaKey] != "true" {34 if messages[0].Meta[shellEscapeMetaKey] != "true" {
M packages/agent/modes/skill_command.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "fmt"4 "fmt"
5 "strings"5 "strings"
6 6
7- "github.com/patriceckhart/zot/packages/agent/skills"7+ "github.com/bnema/zut/packages/agent/skills"
8 )8 )
9 9
10 const skillCommandPrefix = "/skill:"10 const skillCommandPrefix = "/skill:"
M packages/agent/modes/skill_command_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "strings"4 "strings"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/agent/skills"7+ "github.com/bnema/zut/packages/agent/skills"
8 )8 )
9 9
10 func TestExpandSkillCommandForcesNamedSkill(t *testing.T) {10 func TestExpandSkillCommandForcesNamedSkill(t *testing.T) {
M packages/agent/modes/skills_dialog.go +3 -3
@@ -4,8 +4,8 @@ import (
4 "fmt"4 "fmt"
5 "strings"5 "strings"
6 6
7- "github.com/patriceckhart/zot/packages/agent/skills"7+ "github.com/bnema/zut/packages/agent/skills"
8- "github.com/patriceckhart/zot/packages/tui"8+ "github.com/bnema/zut/packages/tui"
9 )9 )
10 10
11 // skillsDialog lists every discovered skill and lets the user view11 // skillsDialog lists every discovered skill and lets the user view
@@ -100,7 +100,7 @@ func (d *skillsDialog) Render(th tui.Theme, width int) []string {
100 out := []string{frameHeader(th, "skills (enter to view, esc to close)", width)}100 out := []string{frameHeader(th, "skills (enter to view, esc to close)", width)}
101 if len(d.skills) == 0 {101 if len(d.skills) == 0 {
102 out = append(out, " "+th.FGColor(th.Muted, "no user skills loaded"))102 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"))
104 out = append(out, frameRule(th, width))104 out = append(out, frameRule(th, width))
105 return out105 return out
106 }106 }
M packages/agent/modes/slash_case_test.go +2 -2
@@ -21,8 +21,8 @@ func TestRunSlashPreservesArgumentCase(t *testing.T) {
21 return errors.New("stop after capture")21 return errors.New("stop after capture")
22 },22 },
23 }}23 }}
24- i.runSlash(context.Background(), "/CD /Users/Pat/Mixed Case")24+ i.runSlash(context.Background(), "/CD /Users/Example/Mixed Case")
25- if got != "/Users/Pat/Mixed Case" {25+ if got != "/Users/Example/Mixed Case" {
26 t.Fatalf("ChangeCWD path = %q, want original argument case", got)26 t.Fatalf("ChangeCWD path = %q, want original argument case", got)
27 }27 }
28 }28 }
M packages/agent/modes/slash_suggest.go +4 -4
@@ -6,8 +6,8 @@ import (
6 6
7 "github.com/mattn/go-runewidth"7 "github.com/mattn/go-runewidth"
8 8
9- "github.com/patriceckhart/zot/packages/agent/skills"9+ "github.com/bnema/zut/packages/agent/skills"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 // slashCommand is one entry in the autocomplete popup. Header rows13 // slashCommand is one entry in the autocomplete popup. Header rows
@@ -67,7 +67,7 @@ var slashCatalog = []slashCommand{
67 {Name: "/telegram", Desc: "connect, disconnect, or show status of the telegram bridge"},67 {Name: "/telegram", Desc: "connect, disconnect, or show status of the telegram bridge"},
68 {Name: "/settings", Desc: "open settings"},68 {Name: "/settings", Desc: "open settings"},
69 {Name: "/clear", Desc: "clear the chat transcript"},69 {Name: "/clear", Desc: "clear the chat transcript"},
70- {Name: "/exit", Desc: "exit zot"},70+ {Name: "/exit", Desc: "exit zut"},
71 }71 }
72 72
73 // slashSuggester renders the popup that appears when the editor starts73 // slashSuggester renders the popup that appears when the editor starts
@@ -205,7 +205,7 @@ func (s *slashSuggester) baseCatalog() []slashCommand {
205 205
206 // looksLikeSlashCommand reports whether text is an attempt at a slash206 // looksLikeSlashCommand reports whether text is an attempt at a slash
207 // command (valid or not). Returns true for things like "/foo" or207 // command (valid or not). Returns true for things like "/foo" or
208-// "/bar baz" but false for paths ("/Users/pat/...") and regexes208+// "/bar baz" but false for paths ("/Users/example/...") and regexes
209 // ("/foo.bar/") so those can be sent to the model as-is.209 // ("/foo.bar/") so those can be sent to the model as-is.
210 //210 //
211 // The head after "/" must be a single simple word: only letters,211 // 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 (
4 "slices"4 "slices"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/agent/skills"7+ "github.com/bnema/zut/packages/agent/skills"
8 )8 )
9 9
10 func TestSlashSuggesterHidesUnjailUntilJailed(t *testing.T) {10 func TestSlashSuggesterHidesUnjailUntilJailed(t *testing.T) {
M packages/agent/modes/spinner.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "math/rand"4 "math/rand"
5 "time"5 "time"
6 6
7- "github.com/patriceckhart/zot/packages/tui"7+ "github.com/bnema/zut/packages/tui"
8 )8 )
9 9
10 // spinner drives the busy animation shown in the status bar while a10 // 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"
4 4
5 func TestStartupResourcesAreOptIn(t *testing.T) {5 func TestStartupResourcesAreOptIn(t *testing.T) {
6 cfg := InteractiveConfig{6 cfg := InteractiveConfig{
7- StartupAgentName: "zot-maintenance",7+ StartupAgentName: "zut-maintenance",
8 StartupContextPaths: []string{"/repo/AGENTS.md"},8 StartupContextPaths: []string{"/repo/AGENTS.md"},
9 StartupExtensionNames: []string{"workspaces"},9 StartupExtensionNames: []string{"workspaces"},
10 StartupSkillNames: []string{"review"},10 StartupSkillNames: []string{"review"},
@@ -30,7 +30,7 @@ func assertStartupResourceCounts(t *testing.T, i *Interactive, want int) {
30 t.Helper()30 t.Helper()
31 wantAgent := ""31 wantAgent := ""
32 if want > 0 {32 if want > 0 {
33- wantAgent = "zot-maintenance"33+ wantAgent = "zut-maintenance"
34 }34 }
35 if got := i.view.StartupAgentName; got != wantAgent {35 if got := i.view.StartupAgentName; got != wantAgent {
36 t.Fatalf("startup agent = %q, want %q", got, wantAgent)36 t.Fatalf("startup agent = %q, want %q", got, wantAgent)
M packages/agent/modes/startup_pre_test.go +8 -8
@@ -7,7 +7,7 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11 )11 )
12 12
13 func applyStartupPreResultForTest(t *testing.T, i *Interactive) {13 func applyStartupPreResultForTest(t *testing.T, i *Interactive) {
@@ -66,9 +66,9 @@ func TestStartupPreShellWithoutAgentThenPrefill(t *testing.T) {
66 i.deferredInitialInput = "after-pre"66 i.deferredInitialInput = "after-pre"
67 i.autoSubmitDeferred = false67 i.autoSubmitDeferred = false
68 68
69- cmd := "printf zot-pre"69+ cmd := "printf zut-pre"
70 if runtime.GOOS == "windows" {70 if runtime.GOOS == "windows" {
71- cmd = "echo zot-pre"71+ cmd = "echo zut-pre"
72 }72 }
73 i.startShellEscape(context.Background(), cmd)73 i.startShellEscape(context.Background(), cmd)
74 74
@@ -99,15 +99,15 @@ func TestStartupPreShellThenAutoSubmitShell(t *testing.T) {
99 i := NewInteractive(InteractiveConfig{Agent: agent, CWD: t.TempDir()})99 i := NewInteractive(InteractiveConfig{Agent: agent, CWD: t.TempDir()})
100 i.runCtx = context.Background()100 i.runCtx = context.Background()
101 i.awaitingStartupPre = true101 i.awaitingStartupPre = true
102- i.deferredInitialInput = "!printf zot-default"102+ i.deferredInitialInput = "!printf zut-default"
103 if runtime.GOOS == "windows" {103 if runtime.GOOS == "windows" {
104- i.deferredInitialInput = "!echo zot-default"104+ i.deferredInitialInput = "!echo zut-default"
105 }105 }
106 i.autoSubmitDeferred = true106 i.autoSubmitDeferred = true
107 107
108- cmd := "printf zot-pre"108+ cmd := "printf zut-pre"
109 if runtime.GOOS == "windows" {109 if runtime.GOOS == "windows" {
110- cmd = "echo zot-pre"110+ cmd = "echo zut-pre"
111 }111 }
112 i.startShellEscape(context.Background(), cmd)112 i.startShellEscape(context.Background(), cmd)
113 113
@@ -125,7 +125,7 @@ func TestStartupPreShellThenAutoSubmitShell(t *testing.T) {
125 i.mu.Unlock()125 i.mu.Unlock()
126 if len(messages) >= 2 && !running && !awaiting {126 if len(messages) >= 2 && !running && !awaiting {
127 joined := userMessageText(messages[0]) + "\n" + userMessageText(messages[1])127 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") {
129 t.Fatalf("context = %q, want both pre and default shell output", joined)129 t.Fatalf("context = %q, want both pre and default shell output", joined)
130 }130 }
131 return131 return
M packages/agent/modes/stream.go +2 -2
@@ -7,8 +7,8 @@ import (
7 "os"7 "os"
8 "strings"8 "strings"
9 9
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12 )12 )
13 13
14 // RunStream runs the agent and writes assistant text to out as it14 // 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 (
5 "context"5 "context"
6 "testing"6 "testing"
7 7
8- "github.com/patriceckhart/zot/packages/core"8+ "github.com/bnema/zut/packages/core"
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10 )10 )
11 11
12 type streamTestClient struct {12 type streamTestClient struct {
M packages/agent/modes/subagents_dialog.go +4 -4
@@ -6,8 +6,8 @@ import (
6 "strings"6 "strings"
7 "time"7 "time"
8 8
9- "github.com/patriceckhart/zot/packages/agent/subagents"9+ "github.com/bnema/zut/packages/agent/subagents"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 // subagentsDialog is the dashboard shown by /subagents (no argument) and13 // subagentsDialog is the dashboard shown by /subagents (no argument) and
@@ -132,10 +132,10 @@ func (d *subagentsDialog) editorPrompt(th tui.Theme) string {
132 132
133 // SetCurrentModel pins the model + provider every fresh spawn will133 // SetCurrentModel pins the model + provider every fresh spawn will
134 // inherit. The host wires this to the same Model / Provider the rest134 // inherit. The host wires this to the same Model / Provider the rest
135-// of zot is currently using, so agents started from the dashboard135+// of zut is currently using, so agents started from the dashboard
136 // run on whatever the user last picked via /model. Pass empty136 // run on whatever the user last picked via /model. Pass empty
137 // strings to clear the override (the child then resolves its own137 // 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).
139 func (d *subagentsDialog) SetCurrentModel(model, providerID string) {139 func (d *subagentsDialog) SetCurrentModel(model, providerID string) {
140 d.pendingModel = model140 d.pendingModel = model
141 d.pendingProvider = providerID141 d.pendingProvider = providerID
M packages/agent/modes/subagents_dialog_test.go +3 -3
@@ -9,9 +9,9 @@ import (
9 "testing"9 "testing"
10 "time"10 "time"
11 11
12- "github.com/patriceckhart/zot/packages/agent/subagents"12+ "github.com/bnema/zut/packages/agent/subagents"
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14- "github.com/patriceckhart/zot/packages/tui"14+ "github.com/bnema/zut/packages/tui"
15 )15 )
16 16
17 // staticSnapshots returns a deterministic snapshot slice for the17 // staticSnapshots returns a deterministic snapshot slice for the
M packages/agent/modes/subagents_slash.go +3 -3
@@ -5,9 +5,9 @@ import (
5 "fmt"5 "fmt"
6 "strings"6 "strings"
7 7
8- "github.com/patriceckhart/zot/packages/agent/subagents"8+ "github.com/bnema/zut/packages/agent/subagents"
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10- "github.com/patriceckhart/zot/packages/tui"10+ "github.com/bnema/zut/packages/tui"
11 )11 )
12 12
13 // runSubagents dispatches /subagents subcommands. Layout:13 // runSubagents dispatches /subagents subcommands. Layout:
M packages/agent/modes/subagents_slash_test.go +1 -1
@@ -7,7 +7,7 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/agent/subagents"10+ "github.com/bnema/zut/packages/agent/subagents"
11 )11 )
12 12
13 // newInteractiveForSupervisorTest builds the minimal Interactive scaffolding13 // newInteractiveForSupervisorTest builds the minimal Interactive scaffolding
M packages/agent/modes/telegram/adapter.go +4 -4
@@ -7,8 +7,8 @@ import (
7 "strings"7 "strings"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/agent/modes/bot"10+ "github.com/bnema/zut/packages/agent/modes/bot"
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12 )12 )
13 13
14 // Adapter implements bot.BotAdapter for Telegram.14 // Adapter implements bot.BotAdapter for Telegram.
@@ -31,7 +31,7 @@ func (a *Adapter) Run(ctx context.Context,
31 commandHandler func(bot.Command, bot.InboundMessage),31 commandHandler func(bot.Command, bot.InboundMessage),
32 ) error {32 ) error {
33 if a.Cfg.BotToken == "" {33 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")
35 }35 }
36 me, err := a.Client.GetMe(ctx)36 me, err := a.Client.GetMe(ctx)
37 if err != nil {37 if err != nil {
@@ -121,7 +121,7 @@ func (a *Adapter) handleUpdate(ctx context.Context, u Update,
121 a.Cfg.AllowedUserID = msg.From.ID121 a.Cfg.AllowedUserID = msg.From.ID
122 _ = a.Save(*a.Cfg)122 _ = a.Save(*a.Cfg)
123 _ = a.Client.SendMessage(ctx, msg.Chat.ID,123 _ = 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),
125 msg.MessageID)125 msg.MessageID)
126 return126 return
127 }127 }
M packages/agent/modes/telegram/api_test.go +7 -7
@@ -12,8 +12,8 @@ import (
12 "testing"12 "testing"
13 "time"13 "time"
14 14
15- "github.com/patriceckhart/zot/packages/agent/modes/bot"15+ "github.com/bnema/zut/packages/agent/modes/bot"
16- "github.com/patriceckhart/zot/packages/provider"16+ "github.com/bnema/zut/packages/provider"
17 )17 )
18 18
19 type roundTripFunc func(*http.Request) (*http.Response, error)19 type roundTripFunc func(*http.Request) (*http.Response, error)
@@ -111,7 +111,7 @@ func TestAdapterRunDeletesWebhookBeforePolling(t *testing.T) {
111 mu.Unlock()111 mu.Unlock()
112 switch endpoint {112 switch endpoint {
113 case "getMe":113 case "getMe":
114- return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zot_test"}}`), nil114+ return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zut_test"}}`), nil
115 case "deleteWebhook":115 case "deleteWebhook":
116 return telegramResponse(`{"ok":true,"result":true}`), nil116 return telegramResponse(`{"ok":true,"result":true}`), nil
117 case "getUpdates":117 case "getUpdates":
@@ -122,7 +122,7 @@ func TestAdapterRunDeletesWebhookBeforePolling(t *testing.T) {
122 return nil, errors.New("unreachable")122 return nil, errors.New("unreachable")
123 }123 }
124 }))124 }))
125- cfg := Config{BotToken: "test", BotID: 1, BotUsername: "zot_test"}125+ cfg := Config{BotToken: "test", BotID: 1, BotUsername: "zut_test"}
126 adapter := NewAdapter(client, &cfg, func(Config) error { return nil })126 adapter := NewAdapter(client, &cfg, func(Config) error { return nil })
127 127
128 err := adapter.Run(ctx, func(bot.InboundMessage) {}, func(bot.Command, bot.InboundMessage) {})128 err := adapter.Run(ctx, func(bot.InboundMessage) {}, func(bot.Command, bot.InboundMessage) {})
@@ -151,7 +151,7 @@ func TestBridgeStartDeletesWebhookBeforePolling(t *testing.T) {
151 mu.Unlock()151 mu.Unlock()
152 switch endpoint {152 switch endpoint {
153 case "getMe":153 case "getMe":
154- return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zot_test"}}`), nil154+ return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zut_test"}}`), nil
155 case "deleteWebhook":155 case "deleteWebhook":
156 return telegramResponse(`{"ok":true,"result":true}`), nil156 return telegramResponse(`{"ok":true,"result":true}`), nil
157 case "getUpdates":157 case "getUpdates":
@@ -165,7 +165,7 @@ func TestBridgeStartDeletesWebhookBeforePolling(t *testing.T) {
165 }))165 }))
166 bridge := &Bridge{166 bridge := &Bridge{
167 Client: client,167 Client: client,
168- Config: Config{BotToken: "test", BotID: 1, BotUsername: "zot_test"},168+ Config: Config{BotToken: "test", BotID: 1, BotUsername: "zut_test"},
169 Save: func(Config) error { return nil },169 Save: func(Config) error { return nil },
170 Host: testBridgeHost{},170 Host: testBridgeHost{},
171 }171 }
@@ -201,7 +201,7 @@ func TestAdapterRunFailsBeforePollingWhenWebhookRemovalFails(t *testing.T) {
201 endpoint := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]201 endpoint := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]
202 calls.WriteString(endpoint + "\n")202 calls.WriteString(endpoint + "\n")
203 if endpoint == "getMe" {203 if endpoint == "getMe" {
204- return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zot_test"}}`), nil204+ return telegramResponse(`{"ok":true,"result":{"id":1,"is_bot":true,"username":"zut_test"}}`), nil
205 }205 }
206 return telegramResponse(`{"ok":false,"description":"webhook removal denied","result":false}`), nil206 return telegramResponse(`{"ok":false,"description":"webhook removal denied","result":false}`), nil
207 }))207 }))
M packages/agent/modes/telegram/bridge.go +14 -14
@@ -7,8 +7,8 @@ import (
7 "sync"7 "sync"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/agent/modes/bot"10+ "github.com/bnema/zut/packages/agent/modes/bot"
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12 )12 )
13 13
14 // Host is the small interface the Bridge calls back into the TUI14 // Host is the small interface the Bridge calls back into the TUI
@@ -37,7 +37,7 @@ type Host interface {
37 // Host's running agent, then mirrors the agent's final assistant37 // Host's running agent, then mirrors the agent's final assistant
38 // text back to the paired Telegram user. One bridge per Interactive38 // text back to the paired Telegram user. One bridge per Interactive
39 // instance; created on /telegram connect, stopped on /telegram39 // instance; created on /telegram connect, stopped on /telegram
40-// disconnect or zot exit.40+// disconnect or zut exit.
41 type Bridge struct {41 type Bridge struct {
42 Client *Client42 Client *Client
43 Config Config43 Config Config
@@ -52,10 +52,10 @@ type Bridge struct {
52 workingCancel context.CancelFunc52 workingCancel context.CancelFunc
53 53
54 // nextReplyFromTelegram is set when the next assistant reply54 // nextReplyFromTelegram is set when the next assistant reply
55- // should be sent bare (no "zot: " prefix) because the turn was55+ // should be sent bare (no "zut: " prefix) because the turn was
56 // initiated by a Telegram DM. The flag clears as soon as the56 // initiated by a Telegram DM. The flag clears as soon as the
57 // reply is flushed. TUI-originated turns leave the flag false57 // reply is flushed. TUI-originated turns leave the flag false
58- // so the reply is tagged "zot: " for clarity on the two-sided58+ // so the reply is tagged "zut: " for clarity on the two-sided
59 // transcript.59 // transcript.
60 nextReplyFromTelegram bool60 nextReplyFromTelegram bool
61 }61 }
@@ -63,7 +63,7 @@ type Bridge struct {
63 // State is the snapshot /telegram status reports.63 // State is the snapshot /telegram status reports.
64 type State struct {64 type State struct {
65 Running bool65 Running bool
66- Username string // bot username, e.g. "zotbot"66+ Username string // bot username, e.g. "zutbot"
67 PairedID int64 // 0 when no user has claimed the bot yet67 PairedID int64 // 0 when no user has claimed the bot yet
68 }68 }
69 69
@@ -103,7 +103,7 @@ func (b *Bridge) Start(parent context.Context) error {
103 }103 }
104 if b.Config.BotToken == "" {104 if b.Config.BotToken == "" {
105 b.mu.Unlock()105 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")
107 }107 }
108 b.mu.Unlock()108 b.mu.Unlock()
109 109
@@ -160,14 +160,14 @@ func (b *Bridge) Stop() {
160 // OnAssistantText should be called by the TUI with the assistant's160 // OnAssistantText should be called by the TUI with the assistant's
161 // final visible text for each turn. The bridge forwards it to the161 // final visible text for each turn. The bridge forwards it to the
162 // paired chat in message-sized chunks. Prefix depends on which162 // paired chat in message-sized chunks. Prefix depends on which
163-// side initiated the turn: TUI-originated turns get "zot: " so the163+// side initiated the turn: TUI-originated turns get "zut: " so the
164-// two-sided transcript reads naturally ("you: ..." / "zot: ..."),164+// two-sided transcript reads naturally ("you: ..." / "zut: ..."),
165 // while Telegram-originated turns send bare text (the user's own165 // while Telegram-originated turns send bare text (the user's own
166-// bubble is already on-screen, a "zot: " prefix would just add166+// bubble is already on-screen, a "zut: " prefix would just add
167 // visual noise to a plain back-and-forth).167 // visual noise to a plain back-and-forth).
168 func (b *Bridge) OnAssistantText(text string) {168 func (b *Bridge) OnAssistantText(text string) {
169 b.mu.Lock()169 b.mu.Lock()
170- // prefix := "zot: "170+ // prefix := "zut: "
171 prefix := ""171 prefix := ""
172 workingCancel := b.workingCancel172 workingCancel := b.workingCancel
173 b.workingCancel = nil173 b.workingCancel = nil
@@ -182,7 +182,7 @@ func (b *Bridge) OnAssistantText(text string) {
182 b.sendToPaired(text, prefix)182 b.sendToPaired(text, prefix)
183 }183 }
184 184
185-// OnUserTyped mirrors a message the user typed in the zot TUI into185+// OnUserTyped mirrors a message the user typed in the zut TUI into
186 // the paired Telegram chat, tagged "you:" so the Telegram thread186 // the paired Telegram chat, tagged "you:" so the Telegram thread
187 // stays a complete record of the conversation (both TUI-originated187 // stays a complete record of the conversation (both TUI-originated
188 // and Telegram-originated turns). Messages sent from Telegram188 // and Telegram-originated turns). Messages sent from Telegram
@@ -362,7 +362,7 @@ func (b *Bridge) handleUpdate(ctx context.Context, u Update) {
362 _ = b.Save(b.Config)362 _ = b.Save(b.Config)
363 b.mu.Unlock()363 b.mu.Unlock()
364 _ = b.Client.SendMessage(ctx, msg.Chat.ID,364 _ = 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),
366 msg.MessageID)366 msg.MessageID)
367 b.Host.Notify("success", fmt.Sprintf("telegram paired with user %d (@%s)", msg.From.ID, msg.From.Username))367 b.Host.Notify("success", fmt.Sprintf("telegram paired with user %d (@%s)", msg.From.ID, msg.From.Username))
368 return368 return
@@ -391,7 +391,7 @@ func (b *Bridge) handleUpdate(ctx context.Context, u Update) {
391 switch command {391 switch command {
392 case bot.CmdStart, bot.CmdHelp:392 case bot.CmdStart, bot.CmdHelp:
393 _ = b.Client.SendMessage(ctx, msg.Chat.ID,393 _ = 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.",
395 msg.MessageID)395 msg.MessageID)
396 case bot.CmdStatus:396 case bot.CmdStatus:
397 _ = b.Client.SendMessage(ctx, msg.Chat.ID, b.Host.Status(), msg.MessageID)397 _ = 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 @@
1 package telegram1 package telegram
2 2
3-import "github.com/patriceckhart/zot/packages/agent/modes/bot"3+import "github.com/bnema/zut/packages/agent/modes/bot"
4 4
5 // isStopCommand is a shim to bot.IsStopCommand for backward compatibility.5 // isStopCommand is a shim to bot.IsStopCommand for backward compatibility.
6 var isStopCommand = bot.IsStopCommand6 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.
2 //2 //
3 // It runs in-process, polling Telegram for DMs and forwarding them to3 // It runs in-process, polling Telegram for DMs and forwarding them to
4 // a core.Agent. Responses stream back as Telegram messages. Images4 // a core.Agent. Responses stream back as Telegram messages. Images
@@ -6,7 +6,7 @@
6 // provider.ImageBlock, so vision-capable models see them the same way6 // provider.ImageBlock, so vision-capable models see them the same way
7 // they would via drag-and-drop in the TUI.7 // they would via drag-and-drop in the TUI.
8 //8 //
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.
10 package telegram10 package telegram
11 11
12 import (12 import (
@@ -26,14 +26,14 @@ type Config struct {
26 }26 }
27 27
28 // ConfigPath returns the path to bot.json.28 // ConfigPath returns the path to bot.json.
29-func ConfigPath(zotHome string) string {29+func ConfigPath(zutHome string) string {
30- return filepath.Join(zotHome, "bot.json")30+ return filepath.Join(zutHome, "bot.json")
31 }31 }
32 32
33 // LoadConfig reads bot.json, returning a zero Config if it doesn't exist.33 // 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) {
35 var c Config35 var c Config
36- b, err := os.ReadFile(ConfigPath(zotHome))36+ b, err := os.ReadFile(ConfigPath(zutHome))
37 if errors.Is(err, os.ErrNotExist) {37 if errors.Is(err, os.ErrNotExist) {
38 return c, nil38 return c, nil
39 }39 }
@@ -47,15 +47,15 @@ func LoadConfig(zotHome string) (Config, error) {
47 }47 }
48 48
49 // SaveConfig writes bot.json atomically.49 // SaveConfig writes bot.json atomically.
50-func SaveConfig(zotHome string, c Config) error {50+func SaveConfig(zutHome string, c Config) error {
51- if err := os.MkdirAll(zotHome, 0o755); err != nil {51+ if err := os.MkdirAll(zutHome, 0o755); err != nil {
52 return err52 return err
53 }53 }
54 b, err := json.MarshalIndent(c, "", " ")54 b, err := json.MarshalIndent(c, "", " ")
55 if err != nil {55 if err != nil {
56 return err56 return err
57 }57 }
58- path := ConfigPath(zotHome)58+ path := ConfigPath(zutHome)
59 tmp := path + ".tmp"59 tmp := path + ".tmp"
60 if err := os.WriteFile(tmp, b, 0o600); err != nil {60 if err := os.WriteFile(tmp, b, 0o600); err != nil {
61 return err61 return err
M packages/agent/modes/telegram/daemon.go +8 -8
@@ -4,30 +4,30 @@ import (
4 "path/filepath"4 "path/filepath"
5 "time"5 "time"
6 6
7- "github.com/patriceckhart/zot/packages/agent/modes/bot"7+ "github.com/bnema/zut/packages/agent/modes/bot"
8 )8 )
9 9
10 // PIDPath returns the location of the bot's pid file.10 // 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") }
12 12
13 // LogPath returns the location of the bot's log file (stdout+stderr13 // LogPath returns the location of the bot's log file (stdout+stderr
14-// from a detached `zot bot start`).14+// from a detached `zut bot start`).
15-func LogPath(zotHome string) string { return filepath.Join(zotHome, "logs", "bot.log") }15+func LogPath(zutHome string) string { return filepath.Join(zutHome, "logs", "bot.log") }
16 16
17 // WritePID persists pid to bot.pid. Overwrites any existing file.17 // 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) }
19 19
20 // ReadPID returns the pid stored in bot.pid, or 0 if the file doesn't20 // ReadPID returns the pid stored in bot.pid, or 0 if the file doesn't
21 // exist. Returns an error for any other read/parse failure.21 // 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)) }
23 23
24 // RemovePID deletes the pid file if it exists.24 // 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)) }
26 26
27 // IsRunning returns (pid, true) if a live process with the recorded27 // IsRunning returns (pid, true) if a live process with the recorded
28 // pid exists, or (pid, false) if the pid file points to a dead process.28 // pid exists, or (pid, false) if the pid file points to a dead process.
29 // Stale pid files are left in place; the caller may remove them.29 // 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)) }
31 31
32 // StopProcess asks pid to exit and waits up to graceful for it to stop,32 // StopProcess asks pid to exit and waits up to graceful for it to stop,
33 // then escalates to a forced kill. Returns nil if the process is gone.33 // 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 @@
1 package telegram1 package telegram
2 2
3-import "github.com/patriceckhart/zot/packages/agent/modes/bot"3+import "github.com/bnema/zut/packages/agent/modes/bot"
4 4
5 // StatusSnapshot is an alias for bot.StatusSnapshot for backward compatibility.5 // StatusSnapshot is an alias for bot.StatusSnapshot for backward compatibility.
6 type StatusSnapshot = bot.StatusSnapshot6 type StatusSnapshot = bot.StatusSnapshot
M packages/agent/modes/telegram/status_test.go +3 -3
@@ -4,14 +4,14 @@ import (
4 "strings"4 "strings"
5 "testing"5 "testing"
6 6
7- "github.com/patriceckhart/zot/packages/provider"7+ "github.com/bnema/zut/packages/provider"
8 )8 )
9 9
10 func TestFormatStatusIncludesModelUsageContextAndCWD(t *testing.T) {10 func TestFormatStatusIncludesModelUsageContextAndCWD(t *testing.T) {
11 got := FormatStatus(StatusSnapshot{11 got := FormatStatus(StatusSnapshot{
12 Provider: "openai",12 Provider: "openai",
13 Model: "gpt-5.5",13 Model: "gpt-5.5",
14- CWD: "/tmp/zot",14+ CWD: "/tmp/zut",
15 Usage: provider.Usage{InputTokens: 961_000, OutputTokens: 10_000, CacheReadTokens: 770_000, CostUSD: 2.749},15 Usage: provider.Usage{InputTokens: 961_000, OutputTokens: 10_000, CacheReadTokens: 770_000, CostUSD: 2.749},
16 Subscription: true,16 Subscription: true,
17 ContextUsed: 44_800,17 ContextUsed: 44_800,
@@ -29,7 +29,7 @@ func TestFormatStatusIncludesModelUsageContextAndCWD(t *testing.T) {
29 "11.2%/400k",29 "11.2%/400k",
30 "state: working",30 "state: working",
31 "queued: 2",31 "queued: 2",
32- "cwd: /tmp/zot",32+ "cwd: /tmp/zut",
33 }33 }
34 for _, want := range wants {34 for _, want := range wants {
35 if !strings.Contains(got, want) {35 if !strings.Contains(got, want) {
M packages/agent/modes/telegram_dialog.go +1 -1
@@ -1,7 +1,7 @@
1 package modes1 package modes
2 2
3 import (3 import (
4- "github.com/patriceckhart/zot/packages/tui"4+ "github.com/bnema/zut/packages/tui"
5 )5 )
6 6
7 // telegramDialog is the picker shown when the user runs `/telegram`7 // 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
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/tui"6+ "github.com/bnema/zut/packages/tui"
7 )7 )
8 8
9 func TestSettingsDialogOffersInheritedTheme(t *testing.T) {9 func TestSettingsDialogOffersInheritedTheme(t *testing.T) {
M packages/agent/modes/update_banner.go +3 -3
@@ -3,7 +3,7 @@ package modes
3 import (3 import (
4 "fmt"4 "fmt"
5 5
6- "github.com/patriceckhart/zot/packages/tui"6+ "github.com/bnema/zut/packages/tui"
7 )7 )
8 8
9 // UpdateInfo mirrors agent.UpdateInfo without the import cycle. The9 // UpdateInfo mirrors agent.UpdateInfo without the import cycle. The
@@ -32,10 +32,10 @@ func renderUpdateBanner(th tui.Theme, info UpdateInfo, width int) []string {
32 }32 }
33 out = append(out, "")33 out = append(out, "")
34 34
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)
36 out = append(out, " "+th.FGColor(color, tui.Bold(title)))36 out = append(out, " "+th.FGColor(color, tui.Bold(title)))
37 out = append(out, "")37 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"))
39 39
40 if info.URL != "" {40 if info.URL != "" {
41 out = append(out, " "+th.FGColor(th.Muted, "changelog: ")+th.FGColor(color, info.URL))41 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
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/tui"6+ "github.com/bnema/zut/packages/tui"
7 )7 )
8 8
9 func TestRenderUpdateBannerOmitsUnavailableUpdate(t *testing.T) {9 func TestRenderUpdateBannerOmitsUnavailableUpdate(t *testing.T) {
M packages/agent/modes/welcome.go +6 -6
@@ -1,18 +1,18 @@
1 package modes1 package modes
2 2
3-import "github.com/patriceckhart/zot/packages/tui"3+import "github.com/bnema/zut/packages/tui"
4 4
5 // welcomeBanner returns the intro text shown at the top of an empty chat.5 // 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.
7 //7 //
8 // When version is non-empty AND showVersion is true, the headline8 // 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 on9+// reads "i'm zut (vX.Y.Z). ..." so users see which build they're on
10-// the moment zot starts. After welcomeVersionDuration the caller10+// the moment zut starts. After welcomeVersionDuration the caller
11 // flips showVersion off and the headline reverts to plain text.11 // flips showVersion off and the headline reverts to plain text.
12 func welcomeBanner(th tui.Theme, version string, showVersion bool) []string {12 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."
14 if showVersion && version != "" {14 if showVersion && version != "" {
15- text = "zot (" + version + "). yet another coding agent harness."15+ text = "zut (" + version + "). yet another coding agent harness."
16 }16 }
17 headline := th.AccentBar(th.Assistant) + th.FGColor(th.Assistant, tui.Bold(text))17 headline := th.AccentBar(th.Assistant) + th.FGColor(th.Assistant, tui.Bold(text))
18 return []string{18 return []string{
M packages/agent/ponytail_test.go +8 -8
@@ -54,7 +54,7 @@ func TestResolvePonytailModeDefaultAndExplicitDisable(t *testing.T) {
54 for _, tc := range cases {54 for _, tc := range cases {
55 t.Run(tc.name, func(t *testing.T) {55 t.Run(tc.name, func(t *testing.T) {
56 root := t.TempDir()56 root := t.TempDir()
57- t.Setenv("ZOT_HOME", filepath.Join(root, "zot-home"))57+ t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
58 t.Setenv("HOME", filepath.Join(root, "home"))58 t.Setenv("HOME", filepath.Join(root, "home"))
59 if tc.disabled {59 if tc.disabled {
60 disabled := false60 disabled := false
@@ -94,13 +94,13 @@ func btoi(value bool) int {
94 94
95 func TestResolvePonytailModeRetainsAddendumWithSystemFile(t *testing.T) {95 func TestResolvePonytailModeRetainsAddendumWithSystemFile(t *testing.T) {
96 root := t.TempDir()96 root := t.TempDir()
97- zotHome := filepath.Join(root, "zot-home")97+ zutHome := filepath.Join(root, "zut-home")
98- t.Setenv("ZOT_HOME", zotHome)98+ t.Setenv("ZUT_HOME", zutHome)
99 t.Setenv("HOME", filepath.Join(root, "home"))99 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 {
101 t.Fatal(err)101 t.Fatal(err)
102 }102 }
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 {
104 t.Fatal(err)104 t.Fatal(err)
105 }105 }
106 106
@@ -125,7 +125,7 @@ func TestResolvePonytailModeRetainsAddendumWithSystemFile(t *testing.T) {
125 125
126 func TestResolvePonytailModePrecedesExplicitAppendPrompt(t *testing.T) {126 func TestResolvePonytailModePrecedesExplicitAppendPrompt(t *testing.T) {
127 root := t.TempDir()127 root := t.TempDir()
128- t.Setenv("ZOT_HOME", filepath.Join(root, "zot-home"))128+ t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
129 t.Setenv("HOME", filepath.Join(root, "home"))129 t.Setenv("HOME", filepath.Join(root, "home"))
130 130
131 const explicit = "explicit invocation guidance"131 const explicit = "explicit invocation guidance"
@@ -150,7 +150,7 @@ func TestResolvePonytailModePrecedesExplicitAppendPrompt(t *testing.T) {
150 150
151 func TestResolvePonytailModeAppliesAcrossHeadlessModesAndCustomIdentity(t *testing.T) {151 func TestResolvePonytailModeAppliesAcrossHeadlessModesAndCustomIdentity(t *testing.T) {
152 root := t.TempDir()152 root := t.TempDir()
153- t.Setenv("ZOT_HOME", filepath.Join(root, "zot-home"))153+ t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
154 t.Setenv("HOME", filepath.Join(root, "home"))154 t.Setenv("HOME", filepath.Join(root, "home"))
155 155
156 modes := []Mode{"", ModePrint, ModeStream, ModeJSON, ModeRPC, ModeSubagentWorker}156 modes := []Mode{"", ModePrint, ModeStream, ModeJSON, ModeRPC, ModeSubagentWorker}
@@ -178,7 +178,7 @@ func TestResolvePonytailModeAppliesAcrossHeadlessModesAndCustomIdentity(t *testi
178 }178 }
179 179
180 func TestConfigSettingsStorePersistsPonytailWithoutChangingKnownFields(t *testing.T) {180 func TestConfigSettingsStorePersistsPonytailWithoutChangingKnownFields(t *testing.T) {
181- t.Setenv("ZOT_HOME", t.TempDir())181+ t.Setenv("ZUT_HOME", t.TempDir())
182 if err := SaveConfig(Config{Theme: "dark", Provider: "ollama", Model: "local"}); err != nil {182 if err := SaveConfig(Config{Theme: "dark", Provider: "ollama", Model: "local"}); err != nil {
183 t.Fatal(err)183 t.Fatal(err)
184 }184 }
M packages/agent/print_stats_test.go +1 -1
@@ -7,7 +7,7 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 func TestWritePrintStats(t *testing.T) {13 func TestWritePrintStats(t *testing.T) {
M packages/agent/rpc.go +12 -12
@@ -12,12 +12,12 @@ import (
12 "sync"12 "sync"
13 "time"13 "time"
14 14
15- "github.com/patriceckhart/zot/packages/agent/extensions"15+ "github.com/bnema/zut/packages/agent/extensions"
16- "github.com/patriceckhart/zot/packages/agent/extproto"16+ "github.com/bnema/zut/packages/agent/extproto"
17- "github.com/patriceckhart/zot/packages/agent/modes"17+ "github.com/bnema/zut/packages/agent/modes"
18- "github.com/patriceckhart/zot/packages/agent/tools"18+ "github.com/bnema/zut/packages/agent/tools"
19- "github.com/patriceckhart/zot/packages/core"19+ "github.com/bnema/zut/packages/core"
20- "github.com/patriceckhart/zot/packages/provider"20+ "github.com/bnema/zut/packages/provider"
21 )21 )
22 22
23 // runRPCMode implements the JSON-over-stdin/stdout RPC protocol.23 // runRPCMode implements the JSON-over-stdin/stdout RPC protocol.
@@ -42,7 +42,7 @@ import (
42 // ext_widget with extension/id/position/title/lines, and ext_widget_clear42 // ext_widget with extension/id/position/title/lines, and ext_widget_clear
43 // with extension/id when loaded extensions publish persistent UI state.43 // with extension/id when loaded extensions publish persistent UI state.
44 //44 //
45-// Auth: if $ZOTCORE_RPC_TOKEN is set, the first command must be45+// Auth: if $ZUTCORE_RPC_TOKEN is set, the first command must be
46 // {"type":"hello","token":"..."} or the connection is closed.46 // {"type":"hello","token":"..."} or the connection is closed.
47 func runRPCMode(ctx context.Context, args Args, version string) error {47 func runRPCMode(ctx context.Context, args Args, version string) error {
48 if args.NoYolo {48 if args.NoYolo {
@@ -69,7 +69,7 @@ func runRPCMode(ctx context.Context, args Args, version string) error {
69 // host-hooks integration. Notify/Display calls from extensions69 // host-hooks integration. Notify/Display calls from extensions
70 // emit RPC events instead of TUI lines so any consumer can react.70 // emit RPC events instead of TUI lines so any consumer can react.
71 extHooks := &rpcExtHooks{server: server}71 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)
73 for _, e := range extMgr.LoadExplicit(ctx, args.Exts) {73 for _, e := range extMgr.LoadExplicit(ctx, args.Exts) {
74 fmt.Fprintln(os.Stderr, "extension load:", e)74 fmt.Fprintln(os.Stderr, "extension load:", e)
75 }75 }
@@ -228,7 +228,7 @@ type rpcServer struct {
228 228
229 // inFlight tracks long-running command goroutines so run() can229 // inFlight tracks long-running command goroutines so run() can
230 // wait for them before returning when stdin closes. Without this,230 // wait for them before returning when stdin closes. Without this,
231- // piping a single 'prompt' command into 'zot rpc' would race the231+ // piping a single 'prompt' command into 'zut rpc' would race the
232 // process exit against the agent loop and the prompt would never232 // process exit against the agent loop and the prompt would never
233 // produce output.233 // produce output.
234 inFlight sync.WaitGroup234 inFlight sync.WaitGroup
@@ -236,10 +236,10 @@ type rpcServer struct {
236 236
237 // run reads NDJSON commands from in and dispatches them. Returns when237 // run reads NDJSON commands from in and dispatches them. Returns when
238 // in is closed AND every in-flight long-running command (prompt /238 // in is closed AND every in-flight long-running command (prompt /
239-// compact) has finished, so a quick `echo cmd | zot rpc` invocation239+// compact) has finished, so a quick `echo cmd | zut rpc` invocation
240 // still produces full output before the process exits.240 // still produces full output before the process exits.
241 func (s *rpcServer) run(in io.Reader) error {241 func (s *rpcServer) run(in io.Reader) error {
242- requireToken := os.Getenv("ZOTCORE_RPC_TOKEN") != ""242+ requireToken := os.Getenv("ZUTCORE_RPC_TOKEN") != ""
243 s.writeMu.Lock()243 s.writeMu.Lock()
244 s.started = true244 s.started = true
245 s.authed = !requireToken245 s.authed = !requireToken
@@ -272,7 +272,7 @@ func (s *rpcServer) run(in io.Reader) error {
272 Token string `json:"token"`272 Token string `json:"token"`
273 }273 }
274 _ = json.Unmarshal([]byte(line), &hello)274 _ = json.Unmarshal([]byte(line), &hello)
275- if hello.Token != os.Getenv("ZOTCORE_RPC_TOKEN") {275+ if hello.Token != os.Getenv("ZUTCORE_RPC_TOKEN") {
276 s.writeError(head.ID, head.Type, "invalid token")276 s.writeError(head.ID, head.Type, "invalid token")
277 return fmt.Errorf("rpc: bad auth token")277 return fmt.Errorf("rpc: bad auth token")
278 }278 }
M packages/agent/rpc_alert_test.go +2 -2
@@ -6,7 +6,7 @@ import (
6 "strings"6 "strings"
7 "testing"7 "testing"
8 8
9- "github.com/patriceckhart/zot/packages/agent/extproto"9+ "github.com/bnema/zut/packages/agent/extproto"
10 )10 )
11 11
12 func TestRPCExtAlertIsJSONOnly(t *testing.T) {12 func TestRPCExtAlertIsJSONOnly(t *testing.T) {
@@ -27,7 +27,7 @@ func TestRPCExtAlertIsJSONOnly(t *testing.T) {
27 }27 }
28 28
29 func TestRPCExtAlertWaitsForAuthentication(t *testing.T) {29 func TestRPCExtAlertWaitsForAuthentication(t *testing.T) {
30- t.Setenv("ZOTCORE_RPC_TOKEN", "synthetic-token")30+ t.Setenv("ZUTCORE_RPC_TOKEN", "synthetic-token")
31 31
32 var out bytes.Buffer32 var out bytes.Buffer
33 server := &rpcServer{out: &out, provider: "test", model: "test-model", version: "test-version"}33 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 (
5 "strings"5 "strings"
6 "testing"6 "testing"
7 7
8- "github.com/patriceckhart/zot/packages/core"8+ "github.com/bnema/zut/packages/core"
9 )9 )
10 10
11 func TestRPCSetReasoningMax(t *testing.T) {11 func TestRPCSetReasoningMax(t *testing.T) {
M packages/agent/sdk/reasoning_test.go +1 -1
@@ -3,7 +3,7 @@ package sdk
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/core"6+ "github.com/bnema/zut/packages/core"
7 )7 )
8 8
9 func TestRuntimeSetReasoningMax(t *testing.T) {9 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 agent1+// Package sdk is the public Go SDK for embedding the zut agent
2 // runtime in third-party programs. It is the only stable, importable2 // runtime in third-party programs. It is the only stable, importable
3 // surface the project exposes; everything under internal/ is subject3 // surface the project exposes; everything under internal/ is subject
4 // to change without notice.4 // to change without notice.
@@ -19,7 +19,7 @@
19 // Runtime per project / cwd. The Cancel call interrupts the active19 // Runtime per project / cwd. The Cancel call interrupts the active
20 // prompt; subsequent prompts work normally.20 // prompt; subsequent prompts work normally.
21 //21 //
22-// For a non-Go consumer, run `zot rpc` and speak the same JSON22+// For a non-Go consumer, run `zut rpc` and speak the same JSON
23 // schema over stdin/stdout. See docs/rpc.md.23 // schema over stdin/stdout. See docs/rpc.md.
24 package sdk24 package sdk
25 25
@@ -29,14 +29,14 @@ import (
29 "fmt"29 "fmt"
30 "sync"30 "sync"
31 31
32- "github.com/patriceckhart/zot/packages/agent"32+ "github.com/bnema/zut/packages/agent"
33- agenttools "github.com/patriceckhart/zot/packages/agent/tools"33+ agenttools "github.com/bnema/zut/packages/agent/tools"
34- "github.com/patriceckhart/zot/packages/core"34+ "github.com/bnema/zut/packages/core"
35- "github.com/patriceckhart/zot/packages/provider"35+ "github.com/bnema/zut/packages/provider"
36 )36 )
37 37
38 // Config configures a Runtime. All fields are optional; sensible38 // Config configures a Runtime. All fields are optional; sensible
39-// defaults are read from $ZOT_HOME/config.json, env vars, and the39+// defaults are read from $ZUT_HOME/config.json, env vars, and the
40 // resolver chain (the same one the cli uses).40 // resolver chain (the same one the cli uses).
41 type Config struct {41 type Config struct {
42 // Provider is "anthropic" or "openai". Empty = use the user's42 // Provider is "anthropic" or "openai". Empty = use the user's
@@ -87,7 +87,7 @@ type Config struct {
87 Lock bool87 Lock bool
88 }88 }
89 89
90-// Runtime is one zot agent session. Safe for use from one goroutine90+// Runtime is one zut agent session. Safe for use from one goroutine
91 // at a time per Runtime; create separate Runtimes for parallel work.91 // at a time per Runtime; create separate Runtimes for parallel work.
92 type Runtime struct {92 type Runtime struct {
93 mu sync.Mutex93 mu sync.Mutex
M packages/agent/sdk/types.go +3 -3
@@ -3,14 +3,14 @@ package sdk
3 import (3 import (
4 "encoding/json"4 "encoding/json"
5 5
6- "github.com/patriceckhart/zot/packages/core"6+ "github.com/bnema/zut/packages/core"
7- "github.com/patriceckhart/zot/packages/provider"7+ "github.com/bnema/zut/packages/provider"
8 )8 )
9 9
10 // Event is the unit emitted by Runtime.Prompt's channel. Type10 // Event is the unit emitted by Runtime.Prompt's channel. Type
11 // identifies the event kind; remaining fields are populated based on11 // identifies the event kind; remaining fields are populated based on
12 // kind. The wire schema is the same as the JSON-RPC stream notifications12 // 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.
14 type Event struct {14 type Event struct {
15 Type string `json:"type"`15 Type string `json:"type"`
16 16
M packages/agent/settings_store.go +2 -2
@@ -3,8 +3,8 @@ package agent
3 import (3 import (
4 "fmt"4 "fmt"
5 5
6- "github.com/patriceckhart/zot/packages/provider"6+ "github.com/bnema/zut/packages/provider"
7- "github.com/patriceckhart/zot/packages/tui"7+ "github.com/bnema/zut/packages/tui"
8 )8 )
9 9
10 type configSettingsStore struct{}10 type configSettingsStore struct{}
M packages/agent/settings_store_test.go +7 -7
@@ -3,7 +3,7 @@ package agent
3 import "testing"3 import "testing"
4 4
5 func TestConfigSettingsStorePersistsShowInstructionsAtStartup(t *testing.T) {5 func TestConfigSettingsStorePersistsShowInstructionsAtStartup(t *testing.T) {
6- t.Setenv("ZOT_HOME", t.TempDir())6+ t.Setenv("ZUT_HOME", t.TempDir())
7 if err := SaveConfig(Config{Theme: "dark"}); err != nil {7 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
8 t.Fatal(err)8 t.Fatal(err)
9 }9 }
@@ -24,7 +24,7 @@ func TestConfigSettingsStorePersistsShowInstructionsAtStartup(t *testing.T) {
24 }24 }
25 25
26 func TestConfigSettingsStorePersistsTerminalAlerts(t *testing.T) {26 func TestConfigSettingsStorePersistsTerminalAlerts(t *testing.T) {
27- t.Setenv("ZOT_HOME", t.TempDir())27+ t.Setenv("ZUT_HOME", t.TempDir())
28 if err := SaveConfig(Config{Theme: "dark"}); err != nil {28 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
29 t.Fatal(err)29 t.Fatal(err)
30 }30 }
@@ -45,7 +45,7 @@ func TestConfigSettingsStorePersistsTerminalAlerts(t *testing.T) {
45 }45 }
46 46
47 func TestConfigSettingsStorePersistsTerminalTitle(t *testing.T) {47 func TestConfigSettingsStorePersistsTerminalTitle(t *testing.T) {
48- t.Setenv("ZOT_HOME", t.TempDir())48+ t.Setenv("ZUT_HOME", t.TempDir())
49 if err := SaveConfig(Config{Theme: "dark"}); err != nil {49 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
50 t.Fatal(err)50 t.Fatal(err)
51 }51 }
@@ -66,7 +66,7 @@ func TestConfigSettingsStorePersistsTerminalTitle(t *testing.T) {
66 }66 }
67 67
68 func TestConfigSettingsStorePersistsJailByDefault(t *testing.T) {68 func TestConfigSettingsStorePersistsJailByDefault(t *testing.T) {
69- t.Setenv("ZOT_HOME", t.TempDir())69+ t.Setenv("ZUT_HOME", t.TempDir())
70 if err := SaveConfig(Config{Theme: "dark"}); err != nil {70 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
71 t.Fatal(err)71 t.Fatal(err)
72 }72 }
@@ -87,7 +87,7 @@ func TestConfigSettingsStorePersistsJailByDefault(t *testing.T) {
87 }87 }
88 88
89 func TestConfigSettingsStorePersistsAutoCompactThreshold(t *testing.T) {89 func TestConfigSettingsStorePersistsAutoCompactThreshold(t *testing.T) {
90- t.Setenv("ZOT_HOME", t.TempDir())90+ t.Setenv("ZUT_HOME", t.TempDir())
91 if err := SaveConfig(Config{Theme: "dark"}); err != nil {91 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
92 t.Fatal(err)92 t.Fatal(err)
93 }93 }
@@ -108,7 +108,7 @@ func TestConfigSettingsStorePersistsAutoCompactThreshold(t *testing.T) {
108 }108 }
109 109
110 func TestConfigSettingsStorePersistsLSPDefaults(t *testing.T) {110 func TestConfigSettingsStorePersistsLSPDefaults(t *testing.T) {
111- t.Setenv("ZOT_HOME", t.TempDir())111+ t.Setenv("ZUT_HOME", t.TempDir())
112 if err := SaveConfig(Config{Theme: "dark"}); err != nil {112 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
113 t.Fatal(err)113 t.Fatal(err)
114 }114 }
@@ -163,7 +163,7 @@ func TestConfigLSPEnabledForDefaultsToTrue(t *testing.T) {
163 }163 }
164 164
165 func TestConfigSettingsStorePersistsInheritedTheme(t *testing.T) {165 func TestConfigSettingsStorePersistsInheritedTheme(t *testing.T) {
166- t.Setenv("ZOT_HOME", t.TempDir())166+ t.Setenv("ZUT_HOME", t.TempDir())
167 if err := SaveConfig(Config{Theme: "dark"}); err != nil {167 if err := SaveConfig(Config{Theme: "dark"}); err != nil {
168 t.Fatal(err)168 t.Fatal(err)
169 }169 }
M packages/agent/skills/builtin.go +1 -1
@@ -7,7 +7,7 @@ import (
7 "strings"7 "strings"
8 )8 )
9 9
10-// builtinFS holds the SKILL.md files zot ships with the binary.10+// builtinFS holds the SKILL.md files zut ships with the binary.
11 // They appear in the catalogue as ordinary skills — same on-demand11 // They appear in the catalogue as ordinary skills — same on-demand
12 // load via the `skill` tool, same /skills picker — but never need12 // load via the `skill` tool, same /skills picker — but never need
13 // to be installed by the user. A user-installed skill with the same13 // 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 @@
1 ---1 ---
2-name: write-zot-extension2+name: write-zut-extension
3-description: Help the user create a new zot extension (slash command, LLM tool, or guard) in any language.3+description: Help the user create a new zut extension (slash command, LLM tool, or guard) in any language.
4 ---4 ---
5 5
6-# Writing a zot extension6+# Writing a zut extension
7 7
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 —
9 a new slash command, a new tool the LLM can call, an audit hook, or9 a new slash command, a new tool the LLM can call, an audit hook, or
10 a permission gate. Skim this whole skill first, then collaborate10 a permission gate. Skim this whole skill first, then collaborate
11 with the user on the specific extension they want.11 with the user on the specific extension they want.
12 12
13 ## What an extension is13 ## What an extension is
14 14
15-A zot extension is **an external executable** that zot launches as a15+A zut extension is **an external executable** that zut launches as a
16 subprocess and talks to over its stdin/stdout in newline-delimited16 subprocess and talks to over its stdin/stdout in newline-delimited
17 JSON. It can be written in any language that can read/write JSON17 JSON. It can be written in any language that can read/write JSON
18 lines from stdio: Go, TypeScript (via tsx), Python, Rust, shell with18 lines from stdio: Go, TypeScript (via tsx), Python, Rust, shell with
19 jq, anything. Crash isolation is automatic; one bad extension never19 jq, anything. Crash isolation is automatic; one bad extension never
20-takes down zot.20+takes down zut.
21 21
22 Three things an extension can do (any combination):22 Three things an extension can do (any combination):
23 23
@@ -27,7 +27,7 @@ Three things an extension can do (any combination):
27 (one-shot styled note in the chat), or a "noop".27 (one-shot styled note in the chat), or a "noop".
28 28
29 2. **Tools** — register tools the LLM itself calls. Schema is29 2. **Tools** — register tools the LLM itself calls. Schema is
30- JSON Schema; zot routes the model's `tool_call` to the30+ JSON Schema; zut routes the model's `tool_call` to the
31 extension's `tool_result`. Same lifecycle as built-in tools31 extension's `tool_result`. Same lifecycle as built-in tools
32 (read/write/edit/bash/lsp/skill).32 (read/write/edit/bash/lsp/skill).
33 33
@@ -42,16 +42,16 @@ Three things an extension can do (any combination):
42 Each extension lives in its own directory:42 Each extension lives in its own directory:
43 43
44 ```44 ```
45-~/Library/Application Support/zot/extensions/<name>/45+~/Library/Application Support/zut/extensions/<name>/
46 ├── extension.json # manifest (required)46 ├── extension.json # manifest (required)
47 └── <executable> # whatever exec points at47 └── <executable> # whatever exec points at
48 ```48 ```
49 49
50-Or project-local: `<project>/.zot/extensions/<name>/`. Project-local50+Or project-local: `<project>/.zut/extensions/<name>/`. Project-local
51 wins on name conflict.51 wins on name conflict.
52 52
53 For ad-hoc use during development, skip the install step entirely53 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`).
55 55
56 ### Manifest56 ### Manifest
57 57
@@ -68,7 +68,7 @@ and run `zot --ext PATH` (repeatable: `-e PATH -e PATH`).
68 ```68 ```
69 69
70 Field rules:70 Field rules:
71-- `name` (required, unique) — id zot uses internally; matches the71+- `name` (required, unique) — id zut uses internally; matches the
72 hello frame. Slash commands & tools live in the same name space72 hello frame. Slash commands & tools live in the same name space
73 as built-ins; conflicts are silently shadowed by built-ins.73 as built-ins; conflicts are silently shadowed by built-ins.
74 - `exec` (required) — the executable path. Resolution:74 - `exec` (required) — the executable path. Resolution:
@@ -92,17 +92,18 @@ their responses.
92 The very first frame the extension sends is `hello`:92 The very first frame the extension sends is `hello`:
93 93
94 ```json94 ```json
95-{"type":"hello","name":"weather","version":"1.0.0",95+{"type":"hello","protocol_version":2,"name":"weather","version":"1.0.0",
96 "capabilities":["commands","tools"]}96 "capabilities":["commands","tools"]}
97 ```97 ```
98 98
99 Capabilities are advisory; current values are `commands`, `tools`,99 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.
101 102
102-zot replies with `hello_ack`:103+zut replies with `hello_ack`:
103 104
104 ```json105 ```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",
106 "provider":"anthropic","model":"claude-opus-4-7","cwd":"/path/to/project"}107 "provider":"anthropic","model":"claude-opus-4-7","cwd":"/path/to/project"}
107 ```108 ```
108 109
@@ -110,7 +111,7 @@ zot replies with `hello_ack`:
110 111
111 The canonical startup order is `hello`, wait for `hello_ack`, send112 The canonical startup order is `hello`, wait for `hello_ack`, send
112 registration frames in any order, then send a single `ready` sentinel113 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:
114 115
115 ```json116 ```json
116 {"type":"register_command","name":"weather","description":"current weather"}117 {"type":"register_command","name":"weather","description":"current weather"}
@@ -120,7 +121,7 @@ so zot can finalize the agent's tool registry:
120 {"type":"ready"}121 {"type":"ready"}
121 ```122 ```
122 123
123-If you don't send `ready`, zot's idle watchdog auto-treats you as124+If you don't send `ready`, zut's idle watchdog auto-treats you as
124 ready after 250ms of no frames, but always send it explicitly when125 ready after 250ms of no frames, but always send it explicitly when
125 you can. Newer extensions on faster hosts shave that 250ms off.126 you can. Newer extensions on faster hosts shave that 250ms off.
126 127
@@ -130,7 +131,7 @@ process itself runs from the extension directory, so do not use
130 131
131 ### Runtime frames132 ### Runtime frames
132 133
133-**zot → extension:**134+**zut → extension:**
134 135
135 ```json136 ```json
136 {"type":"command_invoked","id":"abc","name":"weather","args":"berlin"}137 {"type":"command_invoked","id":"abc","name":"weather","args":"berlin"}
@@ -141,7 +142,7 @@ process itself runs from the extension directory, so do not use
141 {"type":"shutdown"}142 {"type":"shutdown"}
142 ```143 ```
143 144
144-**extension → zot (replies + spontaneous notifications):**145+**extension → zut (replies + spontaneous notifications):**
145 146
146 ```json147 ```json
147 {"type":"command_response","id":"abc","action":"prompt",148 {"type":"command_response","id":"abc","action":"prompt",
@@ -171,7 +172,7 @@ stdout.
171 - `"insert"` — drop `insert` into the editor at the cursor172 - `"insert"` — drop `insert` into the editor at the cursor
172 - `"display"` — append `display` to chat as a one-shot note (no173 - `"display"` — append `display` to chat as a one-shot note (no
173 model call, not in transcript)174 model call, not in transcript)
174-- `"noop"` — handled internally; zot doesn't change the UI175+- `"noop"` — handled internally; zut doesn't change the UI
175 176
176 `tool_result.content[]` blocks: `{"type":"text","text":"..."}` or177 `tool_result.content[]` blocks: `{"type":"text","text":"..."}` or
177 `{"type":"image","mime_type":"image/png","data":"<base64>"}`.178 `{"type":"image","mime_type":"image/png","data":"<base64>"}`.
@@ -186,7 +187,7 @@ never stalls the agent.
186 stdout that isn't a JSON frame breaks the wire. The first stdout187 stdout that isn't a JSON frame breaks the wire. The first stdout
187 frame must be `hello`; do not send `notify`, `alert`, logs, or188 frame must be `hello`; do not send `notify`, `alert`, logs, or
188 registration frames before that handshake starts. Use stderr for189 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`).
190 - **One JSON object per line.** No multi-line JSON. Always end191 - **One JSON object per line.** No multi-line JSON. Always end
191 every frame with `\n`.192 every frame with `\n`.
192 - **Flush after writing.** Most stdout writes are line-buffered when193 - **Flush after writing.** Most stdout writes are line-buffered when
@@ -204,7 +205,7 @@ package main
204 205
205 import (206 import (
206 "encoding/json"207 "encoding/json"
207- "github.com/patriceckhart/zot/packages/agent/ext"208+ "github.com/bnema/zut/packages/agent/ext"
208 )209 )
209 210
210 func main() {211 func main() {
@@ -243,7 +244,7 @@ func main() {
243 Build: `go build -o weather .`244 Build: `go build -o weather .`
244 245
245 `OnHello` is optional. Use it when configuration or registrations need246 `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`,
247 `ExtensionDir`, or `DataDir`. The SDK sends `hello`, waits for248 `ExtensionDir`, or `DataDir`. The SDK sends `hello`, waits for
248 `hello_ack`, runs `OnHello`, announces registrations, then sends `ready`.249 `hello_ack`, runs `OnHello`, announces registrations, then sends `ready`.
249 250
@@ -253,7 +254,7 @@ host metadata such as `HostInfo.CWD`, `Provider`, `Model`, `ZotVersion`,
253 ```254 ```
254 255
255 After the hello handshake completes inside `Run`, call `e.Alert` from a256 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:
257 258
258 ```go259 ```go
259 e.Alert(ext.AlertRequest{Kind: ext.AlertKindBell, Reason: "question_ready"})260 e.Alert(ext.AlertRequest{Kind: ext.AlertKindBell, Reason: "question_ready"})
@@ -275,13 +276,14 @@ import { stderr, stdin, stdout } from "node:process";
275 function send(o: object) { stdout.write(JSON.stringify(o) + "\n"); }276 function send(o: object) { stdout.write(JSON.stringify(o) + "\n"); }
276 function log(s: string) { stderr.write(`[scratchpad] ${s}\n`); }277 function log(s: string) { stderr.write(`[scratchpad] ${s}\n`); }
277 278
278-send({ type: "hello", name: "scratchpad", version: "1.0.0",279+send({ type: "hello", protocol_version: 2, name: "scratchpad", version: "1.0.0",
279 capabilities: ["commands", "tools"] });280 capabilities: ["commands", "tools"] });
280 281
281 const rl = createInterface({ input: stdin, crlfDelay: Infinity });282 const rl = createInterface({ input: stdin, crlfDelay: Infinity });
282 rl.on("line", (line) => {283 rl.on("line", (line) => {
283 const f = JSON.parse(line);284 const f = JSON.parse(line);
284 if (f.type === "hello_ack") {285 if (f.type === "hello_ack") {
286+ if (f.protocol_version !== 2) throw new Error("unsupported extension protocol version");
285 // f.cwd is the user's project directory.287 // f.cwd is the user's project directory.
286 send({ type: "register_command", name: "note", description: "append a note" });288 send({ type: "register_command", name: "note", description: "append a note" });
287 send({ type: "register_tool", name: "read_notes",289 send({ type: "register_tool", name: "read_notes",
@@ -317,11 +319,13 @@ import json, sys
317 319
318 def emit(o): sys.stdout.write(json.dumps(o) + "\n"); sys.stdout.flush()320 def emit(o): sys.stdout.write(json.dumps(o) + "\n"); sys.stdout.flush()
319 321
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"]})
321 323
322 for line in sys.stdin:324 for line in sys.stdin:
323 msg = json.loads(line)325 msg = json.loads(line)
324 if msg["type"] == "hello_ack":326 if msg["type"] == "hello_ack":
327+ if msg.get("protocol_version") != 2:
328+ raise SystemExit("unsupported extension protocol version")
325 # msg["cwd"] is the user's project directory.329 # msg["cwd"] is the user's project directory.
326 emit({"type": "register_command", "name": "hellopy", "description": "say hi (python)"})330 emit({"type": "register_command", "name": "hellopy", "description": "say hi (python)"})
327 emit({"type": "ready"})331 emit({"type": "ready"})
@@ -338,24 +342,24 @@ for line in sys.stdin:
338 ## Install / dev workflow342 ## Install / dev workflow
339 343
340 ```bash344 ```bash
341-zot ext install ./weather # copy into $ZOT_HOME/extensions/345+zut ext install ./weather # copy into $ZUT_HOME/extensions/
342-zot ext install --build=go ./weather # explicitly build local Go source346+zut ext install --build=go ./weather # explicitly build local Go source
343-zot --ext ./weather # run from disk for one zot session (no install)347+zut --ext ./weather # run from disk for one zut session (no install)
344-zot --ext . # cwd is the extension dir348+zut --ext . # cwd is the extension dir
345-zot ext list # show installed extensions349+zut ext list # show installed extensions
346-zot ext logs weather # cat the extension's stderr350+zut ext logs weather # cat the extension's stderr
347-zot ext logs weather -f # tail it351+zut ext logs weather -f # tail it
348-zot ext disable weather # keep installed but skip on launch352+zut ext disable weather # keep installed but skip on launch
349-zot ext enable weather353+zut ext enable weather
350-zot ext remove weather354+zut ext remove weather
351 ```355 ```
352 356
353 For TS / Python extensions, no build step is needed — edit the source357 For TS / Python extensions, no build step is needed — edit the source
354-in place and relaunch zot.358+in place and relaunch zut.
355 359
356-For Go, either run `zot ext install --build=go <path>` to explicitly build360+For Go, either run `zut ext install --build=go <path>` to explicitly build
357 the local source into the staged install, or run `go build -o <name> .`361 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 a362+first and then use `zut ext install <path>`. The default install never runs a
359 build step; a missing local executable is rejected with an explicit builder363 build step; a missing local executable is rejected with an explicit builder
360 hint instead of leaving a broken installed extension. The `--build=go` option364 hint instead of leaving a broken installed extension. The `--build=go` option
361 is intentionally limited to local source paths and does not apply to git URLs.365 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:
367 371
368 ```bash372 ```bash
369 {373 {
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"}'
371 sleep 0.2375 sleep 0.2
372 printf '%s\n' '{"type":"command_invoked","id":"1","name":"weather","args":"Berlin"}'376 printf '%s\n' '{"type":"command_invoked","id":"1","name":"weather","args":"Berlin"}'
373 sleep 0.5377 sleep 0.5
@@ -376,7 +380,7 @@ to see exactly what's happening on the wire:
376 ```380 ```
377 381
378 Compare what comes out of stdout to the expected wire format. If a382 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 and383+frame doesn't match what zut expects, it's discarded silently and
380 logged to `ext-<name>.log`.384 logged to `ext-<name>.log`.
381 385
382 ## Process to follow with the user386 ## Process to follow with the user
@@ -391,10 +395,10 @@ logged to `ext-<name>.log`.
391 if they prefer JS-flavored ergonomics; **Python** for one-off395 if they prefer JS-flavored ergonomics; **Python** for one-off
392 scripts.396 scripts.
393 4. Write the extension dir (manifest + source).397 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.
395 For TS / Python, mark the script executable.399 For TS / Python, mark the script executable.
396-6. Suggest `zot --ext <path>` for testing without committing to an install.400+6. Suggest `zut --ext <path>` for testing without committing to an install.
397-7. When happy, `zot ext install <path>` (or `--build=go` for local Go source).401+7. When happy, `zut ext install <path>` (or `--build=go` for local Go source).
398 402
399 Don't try to write a full SDK or framework on top of the protocol403 Don't try to write a full SDK or framework on top of the protocol
400 unless the user asked for one — the wire format is small enough404 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 @@
1 ---1 ---
2-name: write-zot-themes2+name: write-zut-themes
3-description: Help the user create, install, or package zot themes, including theme-only extensions.3+description: Help the user create, install, or package zut themes, including theme-only extensions.
4 ---4 ---
5 5
6-# Writing zot themes6+# Writing zut themes
7 7
8 Use this skill when the user asks for help creating, editing,8 Use this skill when the user asks for help creating, editing,
9-installing, debugging, or packaging a zot color theme. Read this skill9+installing, debugging, or packaging a zut color theme. Read this skill
10 before generating a theme file or advising on theme extension layout.10 before generating a theme file or advising on theme extension layout.
11 11
12-## What a zot theme is12+## What a zut theme is
13 13
14-A zot theme is a JSON file that overrides any subset of zot's built-in14+A zut theme is a JSON file that overrides any subset of zut's built-in
15 light/dark theme values. Theme files are intentionally permissive:15 light/dark theme values. Theme files are intentionally permissive:
16 nothing is required. A file may contain only colors, only spinner16 nothing is required. A file may contain only colors, only spinner
17 settings, only syntax colors, or only metadata. Missing values always17 settings, only syntax colors, or only metadata. Missing values always
@@ -27,19 +27,19 @@ unavailable.
27 User themes are discovered from:27 User themes are discovered from:
28 28
29 ```text29 ```text
30-$ZOT_HOME/themes/*.json30+$ZUT_HOME/themes/*.json
31 ```31 ```
32 32
33 Extension themes are discovered in-place from loaded extension dirs:33 Extension themes are discovered in-place from loaded extension dirs:
34 34
35 ```text35 ```text
36-$ZOT_HOME/extensions/<extension>/theme.json36+$ZUT_HOME/extensions/<extension>/theme.json
37-$ZOT_HOME/extensions/<extension>/themes/theme.json37+$ZUT_HOME/extensions/<extension>/themes/theme.json
38-<project>/.zot/extensions/<extension>/theme.json38+<project>/.zut/extensions/<extension>/theme.json
39-<project>/.zot/extensions/<extension>/themes/theme.json39+<project>/.zut/extensions/<extension>/themes/theme.json
40 ```40 ```
41 41
42-zot does **not** copy extension themes into `$ZOT_HOME/themes`; extension42+zut does **not** copy extension themes into `$ZUT_HOME/themes`; extension
43 owned themes stay in the extension directory. The settings picker stores43 owned themes stay in the extension directory. The settings picker stores
44 an absolute path for extension-owned themes and loads that file directly.44 an absolute path for extension-owned themes and loads that file directly.
45 45
@@ -47,12 +47,12 @@ an absolute path for extension-owned themes and loads that file directly.
47 47
48 ### Empty / metadata-only theme48 ### Empty / metadata-only theme
49 49
50-Valid; inherits all colors/spinner/syntax from zot defaults.50+Valid; inherits all colors/spinner/syntax from zut defaults.
51 51
52 ```json52 ```json
53 {53 {
54 "name": "my-theme",54 "name": "my-theme",
55- "description": "Metadata only; all visuals inherit zot defaults."55+ "description": "Metadata only; all visuals inherit zut defaults."
56 }56 }
57 ```57 ```
58 58
@@ -95,7 +95,7 @@ Top-level spinner overrides are valid and apply to both modes.
95 95
96 ### Dark-only theme also works in light terminals96 ### Dark-only theme also works in light terminals
97 97
98-If `colors.light` is missing, zot applies `colors.dark` overrides on98+If `colors.light` is missing, zut applies `colors.dark` overrides on
99 top of the built-in light default when running in light mode. The99 top of the built-in light default when running in light mode. The
100 inverse is also true: if dark is missing but light exists, light100 inverse is also true: if dark is missing but light exists, light
101 settings are used on dark defaults.101 settings are used on dark defaults.
@@ -103,7 +103,7 @@ settings are used on dark defaults.
103 ```json103 ```json
104 {104 {
105 "name": "custom-spinner",105 "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.",
107 "colors": {107 "colors": {
108 "dark": {108 "dark": {
109 "spinner_frames": ["◢", "◣", "◤", "◥"],109 "spinner_frames": ["◢", "◣", "◤", "◥"],
@@ -123,7 +123,7 @@ All fields below are optional.
123 "name": "my-theme",123 "name": "my-theme",
124 "description": "Shown in /settings → color theme.",124 "description": "Shown in /settings → color theme.",
125 "color_descriptions": {125 "color_descriptions": {
126- "accent": "Optional documentation for humans. zot ignores this object."126+ "accent": "Optional documentation for humans. zut ignores this object."
127 },127 },
128 "colors": {128 "colors": {
129 "dark": {129 "dark": {
@@ -198,7 +198,7 @@ under `colors`, when they should apply to both modes:
198 - `user` — user role label color; mostly compatibility.198 - `user` — user role label color; mostly compatibility.
199 - `user_bubble_bg` — background behind user message rows.199 - `user_bubble_bg` — background behind user message rows.
200 - `user_bubble_fg` — foreground inside user message rows.200 - `user_bubble_fg` — foreground inside user message rows.
201-- `assistant` — assistant/zot accent and spinner text.201+- `assistant` — assistant/zut accent and spinner text.
202 - `tool` — tool names, success marks, diff additions.202 - `tool` — tool names, success marks, diff additions.
203 - `tool_out` — plain tool-output text.203 - `tool_out` — plain tool-output text.
204 - `error` — errors, refused calls, diff deletions.204 - `error` — errors, refused calls, diff deletions.
@@ -230,7 +230,7 @@ Spinner settings can appear at top level, under `colors`, or under
230 230
231 - `spinner_frames` — list of frame strings. Use single-cell glyphs231 - `spinner_frames` — list of frame strings. Use single-cell glyphs
232 when possible so status-bar alignment stays clean.232 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.
234 - `spinner_interval_ms` — frame interval in milliseconds; must be234 - `spinner_interval_ms` — frame interval in milliseconds; must be
235 positive. Missing/invalid falls back to 80ms.235 positive. Missing/invalid falls back to 80ms.
236 236
@@ -291,26 +291,26 @@ Example:
291 ## Installing a user theme291 ## Installing a user theme
292 292
293 ```bash293 ```bash
294-mkdir -p "$ZOT_HOME/themes"294+mkdir -p "$ZUT_HOME/themes"
295-cp my-theme.json "$ZOT_HOME/themes/my-theme.json"295+cp my-theme.json "$ZUT_HOME/themes/my-theme.json"
296 ```296 ```
297 297
298-Then open `/settings` and choose **color theme**. zot switches theme298+Then open `/settings` and choose **color theme**. zut switches theme
299-immediately and persists the selection in `$ZOT_HOME/config.json`.299+immediately and persists the selection in `$ZUT_HOME/config.json`.
300 300
301-If a selected theme file is deleted, zot resets the setting to the301+If a selected theme file is deleted, zut resets the setting to the
302 built-in auto/default theme.302 built-in auto/default theme.
303 303
304 ## Theme-only extensions304 ## Theme-only extensions
305 305
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,
307 subprocess, or executable is required when the extension contains a307 subprocess, or executable is required when the extension contains a
308 valid theme file.308 valid theme file.
309 309
310 Layout:310 Layout:
311 311
312 ```text312 ```text
313-$ZOT_HOME/extensions/my-theme-extension/313+$ZUT_HOME/extensions/my-theme-extension/
314 ├── extension.json314 ├── extension.json
315 └── theme.json315 └── theme.json
316 ```316 ```
@@ -318,7 +318,7 @@ $ZOT_HOME/extensions/my-theme-extension/
318 or:318 or:
319 319
320 ```text320 ```text
321-$ZOT_HOME/extensions/my-theme-extension/321+$ZUT_HOME/extensions/my-theme-extension/
322 ├── extension.json322 ├── extension.json
323 └── themes/323 └── themes/
324 └── theme.json324 └── theme.json
@@ -330,13 +330,13 @@ $ZOT_HOME/extensions/my-theme-extension/
330 {330 {
331 "name": "my-theme-extension",331 "name": "my-theme-extension",
332 "version": "1.0.0",332 "version": "1.0.0",
333- "description": "Ships a zot color theme",333+ "description": "Ships a zut color theme",
334 "enabled": true334 "enabled": true
335 }335 }
336 ```336 ```
337 337
338 No `exec` is needed when `theme.json` or `themes/theme.json` exists.338 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.
340 340
341 In `/settings → color theme`, extension themes show source info in341 In `/settings → color theme`, extension themes show source info in
342 the description, e.g. `from extension my-theme-extension — ...`.342 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.
2 //2 //
3 // A skill is a per-folder SKILL.md file with a YAML frontmatter3 // A skill is a per-folder SKILL.md file with a YAML frontmatter
4 // header. Skills live in well-known directories under the project or4 // 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 +
6 // one-line descriptions in the system prompt, and exposes a built-in6 // one-line descriptions in the system prompt, and exposes a built-in
7 // "skill" tool the model uses to pull the full body on demand.7 // "skill" tool the model uses to pull the full body on demand.
8 //8 //
@@ -12,15 +12,15 @@
12 //12 //
13 // Discovery layout (priority order — first match wins per name):13 // Discovery layout (priority order — first match wins per name):
14 //14 //
15-// ./.zot/skills/<name>/SKILL.md — project (native)15+// ./.zut/skills/<name>/SKILL.md — project (native)
16-// $ZOT_HOME/skills/<name>/SKILL.md — global (native)16+// $ZUT_HOME/skills/<name>/SKILL.md — global (native)
17 // ./.claude/skills/<name>/SKILL.md — project (claude-compat)17 // ./.claude/skills/<name>/SKILL.md — project (claude-compat)
18 // ~/.claude/skills/<name>/SKILL.md — global (claude-compat)18 // ~/.claude/skills/<name>/SKILL.md — global (claude-compat)
19 // ./.agents/skills/<name>/SKILL.md — project (agent-compat)19 // ./.agents/skills/<name>/SKILL.md — project (agent-compat)
20 // ~/.agents/skills/<name>/SKILL.md — global (agent-compat)20 // ~/.agents/skills/<name>/SKILL.md — global (agent-compat)
21 //21 //
22 // The compat paths are deliberate: a SKILL.md written for any of22 // The compat paths are deliberate: a SKILL.md written for any of
23-// the related ecosystems works in zot unchanged. User skill roots are23+// the related ecosystems works in zut unchanged. User skill roots are
24 // walked recursively, and nested files can also be addressed by their24 // walked recursively, and nested files can also be addressed by their
25 // slash-separated path relative to the root (for example,25 // slash-separated path relative to the root (for example,
26 // systems-backend/subskills/golang-patterns).26 // systems-backend/subskills/golang-patterns).
@@ -63,7 +63,7 @@ type Skill struct {
63 // Shown in the /skills picker.63 // Shown in the /skills picker.
64 Source string64 Source string
65 65
66- // Builtin marks skills that ship inside the zot binary. They are66+ // Builtin marks skills that ship inside the zut binary. They are
67 // fully active for the model (system-prompt manifest + skill67 // fully active for the model (system-prompt manifest + skill
68 // tool) but hidden from user-facing surfaces like the /skills68 // tool) but hidden from user-facing surfaces like the /skills
69 // picker so users only see skills they actually installed or69 // picker so users only see skills they actually installed or
@@ -121,18 +121,18 @@ func VisibleSkills(in []*Skill) []*Skill {
121 //121 //
122 // Errors per skill are returned alongside the partial result so a122 // Errors per skill are returned alongside the partial result so a
123 // single broken file doesn't suppress the rest.123 // single broken file doesn't suppress the rest.
124-func Discover(zotHome, cwd, userHome string, includeUser bool) ([]*Skill, []error) {124+func Discover(zutHome, cwd, userHome string, includeUser bool) ([]*Skill, []error) {
125- return DiscoverWithBundled(zotHome, cwd, userHome, includeUser, nil)125+ return DiscoverWithBundled(zutHome, cwd, userHome, includeUser, nil)
126 }126 }
127 127
128 // DiscoverWithBundled merges normal user skills with extension-bundled skills128 // DiscoverWithBundled merges normal user skills with extension-bundled skills
129 // and built-ins. Precedence is user/project locations, then bundled skills in129 // and built-ins. Precedence is user/project locations, then bundled skills in
130 // declaration order, then built-ins. The first matching name wins.130 // 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) {
132 var errs []error132 var errs []error
133 seen := map[string]*Skill{}133 seen := map[string]*Skill{}
134 if includeUser {134 if includeUser {
135- errs = append(errs, scanUserSkills(zotHome, cwd, userHome, seen)...)135+ errs = append(errs, scanUserSkills(zutHome, cwd, userHome, seen)...)
136 }136 }
137 for _, dir := range bundled {137 for _, dir := range bundled {
138 errs = append(errs, scanBundledSkills(dir, seen)...)138 errs = append(errs, scanBundledSkills(dir, seen)...)
@@ -250,9 +250,9 @@ func scanBundledSkills(dir BundledSkillDir, seen map[string]*Skill) []error {
250 // populates `seen` with first-match-wins per name or alias. Split out250 // populates `seen` with first-match-wins per name or alias. Split out
251 // so Discover's includeUser=false path doesn't have to skip over a251 // so Discover's includeUser=false path doesn't have to skip over a
252 // giant block.252 // 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 {
254 var errs []error254 var errs []error
255- for _, loc := range searchDirs(zotHome, cwd, userHome) {255+ for _, loc := range searchDirs(zutHome, cwd, userHome) {
256 _ = filepath.WalkDir(loc.dir, func(path string, entry fs.DirEntry, walkErr error) error {256 _ = filepath.WalkDir(loc.dir, func(path string, entry fs.DirEntry, walkErr error) error {
257 if walkErr != nil {257 if walkErr != nil {
258 if !os.IsNotExist(walkErr) {258 if !os.IsNotExist(walkErr) {
@@ -362,7 +362,7 @@ func uniqueSkills(seen map[string]*Skill) []*Skill {
362 // The format is deliberately compact: name, one-line description,362 // The format is deliberately compact: name, one-line description,
363 // and a source pointer telling the model where the full body363 // and a source pointer telling the model where the full body
364 // lives. Built-in skills show "builtin" since their markdown is364 // lives. Built-in skills show "builtin" since their markdown is
365-// embedded in the zot binary and not on the filesystem; user365+// embedded in the zut binary and not on the filesystem; user
366 // skills show their SKILL.md path (shortened with ~ for HOME).366 // skills show their SKILL.md path (shortened with ~ for HOME).
367 //367 //
368 // Loading still goes through the `skill` tool with just the name.368 // Loading still goes through the `skill` tool with just the name.
@@ -391,7 +391,7 @@ func SystemPromptAddendum(skills []*Skill) string {
391 391
392 // skillSourcePointer returns a short tag describing where a skill392 // skillSourcePointer returns a short tag describing where a skill
393 // originates. Built-ins are tagged "builtin" because their markdown393 // originates. Built-ins are tagged "builtin" because their markdown
394-// is embedded in the zot binary and not reachable through the394+// is embedded in the zut binary and not reachable through the
395 // filesystem. User skills are tagged with their SKILL.md path,395 // filesystem. User skills are tagged with their SKILL.md path,
396 // collapsed to use ~ for the user home when possible.396 // collapsed to use ~ for the user home when possible.
397 func skillSourcePointer(s *Skill, home string) string {397 func skillSourcePointer(s *Skill, home string) string {
@@ -438,7 +438,7 @@ type location struct {
438 label string438 label string
439 }439 }
440 440
441-func searchDirs(zotHome, cwd, userHome string) []location {441+func searchDirs(zutHome, cwd, userHome string) []location {
442 var out []location442 var out []location
443 add := func(dir, label string) {443 add := func(dir, label string) {
444 if dir == "" {444 if dir == "" {
@@ -446,16 +446,16 @@ func searchDirs(zotHome, cwd, userHome string) []location {
446 }446 }
447 out = append(out, location{dir: dir, label: label})447 out = append(out, location{dir: dir, label: label})
448 }448 }
449- if extra := os.Getenv("ZOT_AGENT_SKILLS"); extra != "" {449+ if extra := os.Getenv("ZUT_AGENT_SKILLS"); extra != "" {
450 for _, dir := range filepath.SplitList(extra) {450 for _, dir := range filepath.SplitList(extra) {
451 add(dir, "agent")451 add(dir, "agent")
452 }452 }
453 }453 }
454 if cwd != "" {454 if cwd != "" {
455- add(filepath.Join(cwd, ".zot", "skills"), "project")455+ add(filepath.Join(cwd, ".zut", "skills"), "project")
456 }456 }
457- if zotHome != "" {457+ if zutHome != "" {
458- add(filepath.Join(zotHome, "skills"), "global")458+ add(filepath.Join(zutHome, "skills"), "global")
459 }459 }
460 if cwd != "" {460 if cwd != "" {
461 add(filepath.Join(cwd, ".claude", "skills"), "project (claude)")461 add(filepath.Join(cwd, ".claude", "skills"), "project (claude)")
@@ -510,14 +510,14 @@ func splitFrontmatter(raw string) (string, string) {
510 return front, body510 return front, body
511 }511 }
512 512
513-// parseFrontmatter handles the small subset of YAML zot recognizes:513+// parseFrontmatter handles the small subset of YAML zut recognizes:
514 // - simple `key: value` lines514 // - simple `key: value` lines
515 // - `key: [a, b, c]` flow-style lists515 // - `key: [a, b, c]` flow-style lists
516 // - `key:` followed by indented `- item` block lists516 // - `key:` followed by indented `- item` block lists
517 // - nested `key:` followed by indented `subkey: [...]` for permissions517 // - nested `key:` followed by indented `subkey: [...]` for permissions
518 //518 //
519 // Anything more elaborate is ignored. We deliberately avoid a yaml519 // 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.
521 func parseFrontmatter(front string, s *Skill) {521 func parseFrontmatter(front string, s *Skill) {
522 lines := strings.Split(front, "\n")522 lines := strings.Split(front, "\n")
523 i := 0523 i := 0
M packages/agent/skills/skills_test.go +10 -10
@@ -50,9 +50,9 @@ permissions:
50 }50 }
51 51
52 func TestDiscoverProjectAndGlobalPriorityAndDedup(t *testing.T) {52 func TestDiscoverProjectAndGlobalPriorityAndDedup(t *testing.T) {
53- t.Setenv("ZOT_AGENT_SKILLS", "")53+ t.Setenv("ZUT_AGENT_SKILLS", "")
54 tmp := t.TempDir()54 tmp := t.TempDir()
55- zotHome := filepath.Join(tmp, "home")55+ zutHome := filepath.Join(tmp, "home")
56 cwd := filepath.Join(tmp, "proj")56 cwd := filepath.Join(tmp, "proj")
57 57
58 mk := func(dir, name, desc string) {58 mk := func(dir, name, desc string) {
@@ -63,17 +63,17 @@ func TestDiscoverProjectAndGlobalPriorityAndDedup(t *testing.T) {
63 }63 }
64 64
65 // Same skill name in BOTH project and global; project should win.65 // Same skill name in BOTH project and global; project should win.
66- mk(filepath.Join(cwd, ".zot", "skills"), "shared", "project version")66+ mk(filepath.Join(cwd, ".zut", "skills"), "shared", "project version")
67- mk(filepath.Join(zotHome, "skills"), "shared", "global version")67+ mk(filepath.Join(zutHome, "skills"), "shared", "global version")
68 // Unique skill in global only.68 // 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")
70 70
71- skills, errs := Discover(zotHome, cwd, "", true /* includeUser */)71+ skills, errs := Discover(zutHome, cwd, "", true /* includeUser */)
72 if len(errs) > 0 {72 if len(errs) > 0 {
73 t.Fatalf("errs: %v", errs)73 t.Fatalf("errs: %v", errs)
74 }74 }
75 // Expect the two user skills + every built-in shipped with the75 // 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).
77 builtins := loadBuiltins()77 builtins := loadBuiltins()
78 want := 2 + len(builtins)78 want := 2 + len(builtins)
79 if len(skills) != want {79 if len(skills) != want {
@@ -95,10 +95,10 @@ func TestDiscoverProjectAndGlobalPriorityAndDedup(t *testing.T) {
95 }95 }
96 96
97 func TestDiscoverWithBundledSkillsPrecedence(t *testing.T) {97 func TestDiscoverWithBundledSkillsPrecedence(t *testing.T) {
98- t.Setenv("ZOT_AGENT_SKILLS", "")98+ t.Setenv("ZUT_AGENT_SKILLS", "")
99 tmp := t.TempDir()99 tmp := t.TempDir()
100 cwd := filepath.Join(tmp, "project")100 cwd := filepath.Join(tmp, "project")
101- userDir := filepath.Join(cwd, ".zot", "skills")101+ userDir := filepath.Join(cwd, ".zut", "skills")
102 bundleA := filepath.Join(tmp, "ext-a", "skills")102 bundleA := filepath.Join(tmp, "ext-a", "skills")
103 bundleB := filepath.Join(tmp, "ext-b", "skills")103 bundleB := filepath.Join(tmp, "ext-b", "skills")
104 write := func(root, name, description string) {104 write := func(root, name, description string) {
@@ -165,7 +165,7 @@ func TestVisibleSkillsHidesBuiltins(t *testing.T) {
165 }165 }
166 166
167 func TestDiscoverRecursesNestedAgentSkillsAndSupportsPathAlias(t *testing.T) {167 func TestDiscoverRecursesNestedAgentSkillsAndSupportsPathAlias(t *testing.T) {
168- t.Setenv("ZOT_AGENT_SKILLS", "")168+ t.Setenv("ZUT_AGENT_SKILLS", "")
169 tmp := t.TempDir()169 tmp := t.TempDir()
170 home := filepath.Join(tmp, "home")170 home := filepath.Join(tmp, "home")
171 cwd := filepath.Join(tmp, "project")171 cwd := filepath.Join(tmp, "project")
M packages/agent/skills/tool.go +3 -3
@@ -6,8 +6,8 @@ import (
6 "fmt"6 "fmt"
7 "sync"7 "sync"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 // Tool implements core.Tool, exposing a `skill` tool the LLM can call13 // 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(
84 if s == nil {84 if s == nil {
85 return core.ToolResult{85 return core.ToolResult{
86 IsError: true,86 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)}},
88 }, nil88 }, nil
89 }89 }
90 90
M packages/agent/skills/tool_test.go +1 -1
@@ -6,7 +6,7 @@ import (
6 "strings"6 "strings"
7 "testing"7 "testing"
8 8
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10 )10 )
11 11
12 func TestToolLoadsSkillByPathAlias(t *testing.T) {12 func TestToolLoadsSkillByPathAlias(t *testing.T) {
M packages/agent/subagent_worker.go +15 -15
@@ -13,14 +13,14 @@ import (
13 "time"13 "time"
14 "unicode/utf8"14 "unicode/utf8"
15 15
16- "github.com/patriceckhart/zot/packages/agent/modes"16+ "github.com/bnema/zut/packages/agent/modes"
17- "github.com/patriceckhart/zot/packages/agent/subagents"17+ "github.com/bnema/zut/packages/agent/subagents"
18- "github.com/patriceckhart/zot/packages/core"18+ "github.com/bnema/zut/packages/core"
19- "github.com/patriceckhart/zot/packages/provider"19+ "github.com/bnema/zut/packages/provider"
20 )20 )
21 21
22 // runSubagentWorkerMode is the daemon-mode entry point used by every22 // runSubagentWorkerMode is the daemon-mode entry point used by every
23-// subagent-spawned zot subprocess. It's intentionally close in shape to23+// subagent-spawned zut subprocess. It's intentionally close in shape to
24 // runJSONMode but with two key differences:24 // runJSONMode but with two key differences:
25 //25 //
26 // - Lifetime: the process stays alive across many user turns. The26 // - Lifetime: the process stays alive across many user turns. The
@@ -28,7 +28,7 @@ import (
28 // turns arrive through the inbox unix socket at args.SubagentWorker.28 // turns arrive through the inbox unix socket at args.SubagentWorker.
29 //29 //
30 // - Output: every emitted JSON line is also mirrored verbatim into30 // - Output: every emitted JSON line is also mirrored verbatim into
31-// events.jsonl (see ZOT_SUBAGENT_EVENT_LOG) so a separate zot31+// events.jsonl (see ZUT_SUBAGENT_EVENT_LOG) so a separate zut
32 // process can /subagents open this agent and replay its full history32 // process can /subagents open this agent and replay its full history
33 // even after the parent that spawned us is long gone.33 // even after the parent that spawned us is long gone.
34 //34 //
@@ -40,7 +40,7 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
40 if args.SubagentWorker == "" {40 if args.SubagentWorker == "" {
41 return fmt.Errorf("--subagent-worker requires a socket path")41 return fmt.Errorf("--subagent-worker requires a socket path")
42 }42 }
43- if os.Getenv("ZOT_SUBAGENT_CREDENTIAL_STDIN") == "1" {43+ if os.Getenv("ZUT_SUBAGENT_CREDENTIAL_STDIN") == "1" {
44 var inherited subagents.Credential44 var inherited subagents.Credential
45 dec := json.NewDecoder(io.LimitReader(os.Stdin, 1<<20))45 dec := json.NewDecoder(io.LimitReader(os.Stdin, 1<<20))
46 if err := dec.Decode(&inherited); err != nil {46 if err := dec.Decode(&inherited); err != nil {
@@ -99,11 +99,11 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
99 99
100 // Event log is owned by the supervisor's runner via stdout, but100 // Event log is owned by the supervisor's runner via stdout, but
101 // the daemon also writes a redundant copy here when the runner's101 // 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 still102+ // pipe is closed (e.g. parent zut exited but the agent is still
103 // running headless). The env var is set by the runner; if it's103 // running headless). The env var is set by the runner; if it's
104 // empty we silently skip the second mirror.104 // empty we silently skip the second mirror.
105 var logMirror *subagents.EventLog105 var logMirror *subagents.EventLog
106- if path := os.Getenv("ZOT_SUBAGENT_EVENT_LOG"); path != "" {106+ if path := os.Getenv("ZUT_SUBAGENT_EVENT_LOG"); path != "" {
107 logMirror, _ = subagents.OpenEventLog(path)107 logMirror, _ = subagents.OpenEventLog(path)
108 }108 }
109 if logMirror != nil {109 if logMirror != nil {
@@ -111,7 +111,7 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
111 }111 }
112 112
113 em := newSubagentEmitter(os.Stdout, logMirror)113 em := newSubagentEmitter(os.Stdout, logMirror)
114- em.setProtocolIdentity(os.Getenv("ZOT_SUBAGENT_AGENT_ID"))114+ em.setProtocolIdentity(os.Getenv("ZUT_SUBAGENT_AGENT_ID"))
115 em.emit("agent.ready", map[string]any{115 em.emit("agent.ready", map[string]any{
116 "version": version,116 "version": version,
117 "cwd": r.CWD,117 "cwd": r.CWD,
@@ -125,7 +125,7 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
125 defer close(heartbeatDone)125 defer close(heartbeatDone)
126 go func() {126 go func() {
127 interval := 10 * time.Second127 interval := 10 * time.Second
128- if raw := os.Getenv("ZOT_SUBAGENT_HEARTBEAT_INTERVAL"); raw != "" {128+ if raw := os.Getenv("ZUT_SUBAGENT_HEARTBEAT_INTERVAL"); raw != "" {
129 if parsed, parseErr := time.ParseDuration(raw); parseErr == nil && parsed > 0 {129 if parsed, parseErr := time.ParseDuration(raw); parseErr == nil && parsed > 0 {
130 interval = parsed130 interval = parsed
131 }131 }
@@ -148,7 +148,7 @@ func runSubagentWorkerMode(ctx context.Context, args Args, version string) error
148 mu sync.Mutex148 mu sync.Mutex
149 cancelFn context.CancelFunc149 cancelFn context.CancelFunc
150 busyTurn bool150 busyTurn bool
151- turnNo = initialTurnNumber(os.Getenv("ZOT_SUBAGENT_EVENT_LOG"))151+ turnNo = initialTurnNumber(os.Getenv("ZUT_SUBAGENT_EVENT_LOG"))
152 turnWG sync.WaitGroup152 turnWG sync.WaitGroup
153 closing bool153 closing bool
154 shutdown = make(chan struct{})154 shutdown = make(chan struct{})
@@ -390,7 +390,7 @@ func (e *subagentEmitter) emit(typ string, data map[string]any) {
390 390
391 if !e.orphan && err == nil {391 if !e.orphan && err == nil {
392 if _, werr := e.w.Write(line); werr != nil {392 if _, werr := e.w.Write(line); werr != nil {
393- // Supervisor's stdout pipe is gone (parent zot exited but we393+ // Supervisor's stdout pipe is gone (parent zut exited but we
394 // kept running). Switch to mirror-only mode and preserve this394 // kept running). Switch to mirror-only mode and preserve this
395 // event in the durable log.395 // event in the durable log.
396 e.orphan = true396 e.orphan = true
@@ -451,10 +451,10 @@ func boundedResultSummary(text string) string {
451 451
452 func workerOutputLimits() (maxBytes, maxLines int) {452 func workerOutputLimits() (maxBytes, maxLines int) {
453 maxBytes, maxLines = 500_000, 5_000453 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 {
455 maxBytes = value455 maxBytes = value
456 }456 }
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 {
458 maxLines = value458 maxLines = value
459 }459 }
460 return maxBytes, maxLines460 return maxBytes, maxLines
M packages/agent/subagent_worker_test.go +6 -6
@@ -9,21 +9,21 @@ import (
9 "strings"9 "strings"
10 "testing"10 "testing"
11 11
12- "github.com/patriceckhart/zot/packages/agent/subagents"12+ "github.com/bnema/zut/packages/agent/subagents"
13 )13 )
14 14
15 // TestWorkerOutputLimitsUseSupervisorPolicy verifies that the child reads15 // TestWorkerOutputLimitsUseSupervisorPolicy verifies that the child reads
16 // the effective output caps propagated by the supervisor and retains safe16 // the effective output caps propagated by the supervisor and retains safe
17 // defaults for standalone workers.17 // defaults for standalone workers.
18 func TestWorkerOutputLimitsUseSupervisorPolicy(t *testing.T) {18 func TestWorkerOutputLimitsUseSupervisorPolicy(t *testing.T) {
19- t.Setenv("ZOT_SUBAGENT_MAX_OUTPUT_BYTES", "123")19+ t.Setenv("ZUT_SUBAGENT_MAX_OUTPUT_BYTES", "123")
20- t.Setenv("ZOT_SUBAGENT_MAX_OUTPUT_LINES", "7")20+ t.Setenv("ZUT_SUBAGENT_MAX_OUTPUT_LINES", "7")
21 bytesLimit, linesLimit := workerOutputLimits()21 bytesLimit, linesLimit := workerOutputLimits()
22 if bytesLimit != 123 || linesLimit != 7 {22 if bytesLimit != 123 || linesLimit != 7 {
23 t.Fatalf("worker output limits = (%d, %d), want (123, 7)", bytesLimit, linesLimit)23 t.Fatalf("worker output limits = (%d, %d), want (123, 7)", bytesLimit, linesLimit)
24 }24 }
25- t.Setenv("ZOT_SUBAGENT_MAX_OUTPUT_BYTES", "invalid")25+ t.Setenv("ZUT_SUBAGENT_MAX_OUTPUT_BYTES", "invalid")
26- t.Setenv("ZOT_SUBAGENT_MAX_OUTPUT_LINES", "0")26+ t.Setenv("ZUT_SUBAGENT_MAX_OUTPUT_LINES", "0")
27 bytesLimit, linesLimit = workerOutputLimits()27 bytesLimit, linesLimit = workerOutputLimits()
28 if bytesLimit != 500_000 || linesLimit != 5_000 {28 if bytesLimit != 500_000 || linesLimit != 5_000 {
29 t.Fatalf("invalid worker output limits = (%d, %d), want defaults", bytesLimit, linesLimit)29 t.Fatalf("invalid worker output limits = (%d, %d), want defaults", bytesLimit, linesLimit)
@@ -35,7 +35,7 @@ func TestWorkerOutputLimitsUseSupervisorPolicy(t *testing.T) {
35 //35 //
36 // Symptom: events.jsonl held two copies of every event because the36 // Symptom: events.jsonl held two copies of every event because the
37 // child mirrored each event to disk AND the supervisor parsed the37 // child mirrored each event to disk AND the supervisor parsed the
38-// child's stdout and appended each event to disk too. On next zot38+// child's stdout and appended each event to disk too. On next zut
39 // launch the replay produced two transcript lines per real one.39 // launch the replay produced two transcript lines per real one.
40 //40 //
41 // Fix invariant: while stdout writes succeed (i.e. the supervisor is41 // 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 {
47 // the effective provider connection settings from the supervisor.47 // the effective provider connection settings from the supervisor.
48 // Empty values inherit whatever the child resolves on its own from48 // Empty values inherit whatever the child resolves on its own from
49 // config / env / flags. All four settings are persisted in meta.json49 // config / env / flags. All four settings are persisted in meta.json
50- // so Resume keeps using the same provider configuration across zot50+ // so Resume keeps using the same provider configuration across zut
51 // restarts.51 // restarts.
52 Model string52 Model string
53 Provider string53 Provider string
@@ -58,7 +58,7 @@ type Agent struct {
58 Subagent string58 Subagent string
59 59
60 // SessionID, when non-empty, scopes the agent to a particular60 // SessionID, when non-empty, scopes the agent to a particular
61- // host zot session: the dashboard only surfaces agents whose61+ // host zut session: the dashboard only surfaces agents whose
62 // SessionID matches the active session. Empty means "unscoped" and62 // SessionID matches the active session. Empty means "unscoped" and
63 // applies to agents spawned without a session context such as in tests. Set at63 // applies to agents spawned without a session context such as in tests. Set at
64 // Spawn time from Supervisor.activeSession and persisted in64 // Spawn time from Supervisor.activeSession and persisted in
@@ -93,7 +93,7 @@ type Agent struct {
93 // EventLogPath is the durable JSONL event log for this agent.93 // EventLogPath is the durable JSONL event log for this agent.
94 // The runner appends every well-formed event from the child94 // The runner appends every well-formed event from the child
95 // (plus lifecycle events of its own) here. /subagents open in any95 // (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.
97 EventLogPath string97 EventLogPath string
98 98
99 // SessionPath is the child's persistent session file. Surfaced99 // 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) {
192 // supervisor parsed child stdout and called log.Append(ev), AND the192 // supervisor parsed child stdout and called log.Append(ev), AND the
193 // child's mirror appended the same event. Two writes per event193 // child's mirror appended the same event. Two writes per event
194 // landed in the file in quick succession with near-identical194 // 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 —
196 // the user saw every transcript line duplicated.196 // the user saw every transcript line duplicated.
197 //197 //
198 // The write-time fix makes the child's mirror dormant when the198 // 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) {
37 if !a.FastMode {37 if !a.FastMode {
38 t.Fatal("spawned agent FastMode = false, want true")38 t.Fatal("spawned agent FastMode = false, want true")
39 }39 }
40- args := defaultChildArgs("/zot", a, "/session", "/inbox")40+ args := defaultChildArgs("/zut", a, "/session", "/inbox")
41 if !containsArg(args, "--fast-mode") {41 if !containsArg(args, "--fast-mode") {
42 t.Fatalf("child args = %v, want --fast-mode", args)42 t.Fatalf("child args = %v, want --fast-mode", args)
43 }43 }
@@ -91,7 +91,7 @@ func TestSpawnRequestFastModeIsBoundByHostSetting(t *testing.T) {
91 if a.FastMode != tc.want {91 if a.FastMode != tc.want {
92 t.Fatalf("FastMode = %v, want %v", a.FastMode, tc.want)92 t.Fatalf("FastMode = %v, want %v", a.FastMode, tc.want)
93 }93 }
94- args := defaultChildArgs("/zot", a, "/session", "/inbox")94+ args := defaultChildArgs("/zut", a, "/session", "/inbox")
95 wantFlag := "--no-fast-mode"95 wantFlag := "--no-fast-mode"
96 if tc.want {96 if tc.want {
97 wantFlag = "--fast-mode"97 wantFlag = "--fast-mode"
@@ -142,7 +142,7 @@ func TestSupervisorReloadPreservesExplicitFastModeOff(t *testing.T) {
142 }142 }
143 143
144 func TestSubagentWorkerArgsOmitFastModeWhenUnset(t *testing.T) {144 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"})
146 if strings.Contains(strings.Join(args, " "), "--fast-mode") || strings.Contains(strings.Join(args, " "), "--no-fast-mode") {146 if strings.Contains(strings.Join(args, " "), "--fast-mode") || strings.Contains(strings.Join(args, " "), "--no-fast-mode") {
147 t.Fatalf("child args = %v, want fast mode omitted when unset", args)147 t.Fatalf("child args = %v, want fast mode omitted when unset", args)
148 }148 }
@@ -150,7 +150,7 @@ func TestSubagentWorkerArgsOmitFastModeWhenUnset(t *testing.T) {
150 150
151 func TestSubagentWorkerArgsExplicitlyDisablesFastMode(t *testing.T) {151 func TestSubagentWorkerArgsExplicitlyDisablesFastMode(t *testing.T) {
152 args := subagentWorkerArgs(subagentWorkerArgsOpts{152 args := subagentWorkerArgs(subagentWorkerArgsOpts{
153- Exe: "/zot",153+ Exe: "/zut",
154 Dir: "/wt",154 Dir: "/wt",
155 SessionPath: "/s",155 SessionPath: "/s",
156 InboxPath: "/i",156 InboxPath: "/i",
@@ -165,7 +165,7 @@ func TestSubagentWorkerArgsExplicitlyDisablesFastMode(t *testing.T) {
165 }165 }
166 166
167 func TestDefaultChildArgsExplicitlyDisablesFastMode(t *testing.T) {167 func TestDefaultChildArgsExplicitlyDisablesFastMode(t *testing.T) {
168- args := defaultChildArgs("/zot", &Agent{Dir: "/wt"}, "/s", "/i")168+ args := defaultChildArgs("/zut", &Agent{Dir: "/wt"}, "/s", "/i")
169 if !containsArg(args, "--no-fast-mode") {169 if !containsArg(args, "--no-fast-mode") {
170 t.Fatalf("child args = %v, want --no-fast-mode", args)170 t.Fatalf("child args = %v, want --no-fast-mode", args)
171 }171 }
M packages/agent/subagents/inbox.go +2 -2
@@ -142,7 +142,7 @@ func isNoListenerErr(err error) bool {
142 // distinguish a live worker from a stale path. Callers use this probe before142 // distinguish a live worker from a stale path. Callers use this probe before
143 // resume/remove operations to avoid racing a detached worker's session and143 // resume/remove operations to avoid racing a detached worker's session and
144 // workspace ownership.144 // workspace ownership.
145-const inboxProbeLine = "__zot_subagent_probe__"145+const inboxProbeLine = "__zut_subagent_probe__"
146 146
147 func inboxLive(path string) bool {147 func inboxLive(path string) bool {
148 if path == "" {148 if path == "" {
@@ -169,7 +169,7 @@ type Listener struct {
169 path string169 path string
170 ln net.Listener170 ln net.Listener
171 // active is the most recent accepted connection. The protocol171 // active is the most recent accepted connection. The protocol
172- // only expects one supervisor (the parent zot), so newer172+ // only expects one supervisor (the parent zut), so newer
173 // connections preempt older ones — the previous parent173 // connections preempt older ones — the previous parent
174 // presumably crashed.174 // presumably crashed.
175 mu sync.Mutex175 mu sync.Mutex
M packages/agent/subagents/inbox_test.go +2 -2
@@ -22,7 +22,7 @@ func shortSocketDir(t *testing.T) string {
22 if runtime.GOOS == "windows" {22 if runtime.GOOS == "windows" {
23 t.Skip("subagent inbox transport uses Unix-domain sockets; Windows support will use a named-pipe backend")23 t.Skip("subagent inbox transport uses Unix-domain sockets; Windows support will use a named-pipe backend")
24 }24 }
25- dir, err := os.MkdirTemp("/tmp", "zot-in-")25+ dir, err := os.MkdirTemp("/tmp", "zut-in-")
26 if err != nil {26 if err != nil {
27 t.Fatalf("mkdir: %v", err)27 t.Fatalf("mkdir: %v", err)
28 }28 }
@@ -119,7 +119,7 @@ func TestIsNoListenerErrMatchesECONNREFUSED(t *testing.T) {
119 119
120 // TestInboxNewSupervisorPreemptsOld documents that only one120 // TestInboxNewSupervisorPreemptsOld documents that only one
121 // supervisor connection at a time is honoured. The subagent design121 // supervisor connection at a time is honoured. The subagent design
122-// assumes a single parent zot owns each agent; if a second122+// assumes a single parent zut owns each agent; if a second
123 // parent dials, the listener boots the first one. The first123 // parent dials, the listener boots the first one. The first
124 // supervisor's already-delivered messages still land in the124 // supervisor's already-delivered messages still land in the
125 // channel — what we're guarding against is silent dual ownership,125 // channel — what we're guarding against is silent dual ownership,
M packages/agent/subagents/persist.go +5 -5
@@ -5,7 +5,7 @@ package subagents
5 // Every Spawn writes a meta.json next to the agent's events.jsonl and5 // Every Spawn writes a meta.json next to the agent's events.jsonl and
6 // session.json. The file captures the immutable identity bits (id,6 // session.json. The file captures the immutable identity bits (id,
7 // task, branch, dir) plus the paths the runner needs to resume the7 // task, branch, dir) plus the paths the runner needs to resume the
8-// agent later. On a fresh zot launch, Supervisor.Reload() walks8+// agent later. On a fresh zut launch, Supervisor.Reload() walks
9 // <root>/agents/*/meta.json and re-registers every agent it finds in9 // <root>/agents/*/meta.json and re-registers every agent it finds in
10 // StatusDetached so the user can see, view, resume, or remove them10 // StatusDetached so the user can see, view, resume, or remove them
11 // from the dashboard.11 // from the dashboard.
@@ -76,7 +76,7 @@ type agentMeta struct {
76 SessionPath string `json:"session_path"`76 SessionPath string `json:"session_path"`
77 77
78 // SessionID, when non-empty, scopes the agent to a particular78 // SessionID, when non-empty, scopes the agent to a particular
79- // host zot session: the dashboard only shows agents whose79+ // host zut session: the dashboard only shows agents whose
80 // SessionID matches the active session. Older meta.json files80 // SessionID matches the active session. Older meta.json files
81 // (and agents spawned outside of any session, e.g. by tests or81 // (and agents spawned outside of any session, e.g. by tests or
82 // scripted callers that didn't call SetActiveSession) have an82 // scripted callers that didn't call SetActiveSession) have an
@@ -218,7 +218,7 @@ func validRuntimeInboxPath(path, id string) bool {
218 return false218 return false
219 }219 }
220 dir := filepath.Dir(path)220 dir := filepath.Dir(path)
221- if !strings.HasPrefix(filepath.Base(dir), "zot-subagents-") {221+ if !strings.HasPrefix(filepath.Base(dir), "zut-subagents-") {
222 return false222 return false
223 }223 }
224 bases := []string{os.TempDir(), "/tmp"}224 bases := []string{os.TempDir(), "/tmp"}
@@ -558,7 +558,7 @@ func replayEventsIntoAgent(a *Agent, evs []Event) {
558 // Resume re-attaches a Runner to a previously-spawned agent. Durable558 // Resume re-attaches a Runner to a previously-spawned agent. Durable
559 // session and event files are kept, while the transient inbox path is559 // session and event files are kept, while the transient inbox path is
560 // recalculated for the current runtime environment. Use this to560 // 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:
562 //562 //
563 // subagentMgr.Reload()563 // subagentMgr.Reload()
564 // a, err := subagentMgr.Resume(ctx, "alpha-12345")564 // a, err := subagentMgr.Resume(ctx, "alpha-12345")
@@ -627,7 +627,7 @@ func (f *Supervisor) resume(ctx context.Context, id string, resuming bool) (*Age
627 // Rebuild from the meta record so we don't carry stale runner627 // Rebuild from the meta record so we don't carry stale runner
628 // state from a previous incarnation. The inbox is transient and628 // state from a previous incarnation. The inbox is transient and
629 // may have been persisted under an incompatible filesystem by an629 // 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.
631 inboxPath, err := inboxSocketPath(f.cfg.Root, existing.ID)631 inboxPath, err := inboxSocketPath(f.cfg.Root, existing.ID)
632 if err != nil {632 if err != nil {
633 return nil, fmt.Errorf("subagent inbox path: %w", err)633 return nil, fmt.Errorf("subagent inbox path: %w", err)
M packages/agent/subagents/persist_test.go +5 -5
@@ -16,7 +16,7 @@ import (
16 16
17 // TestSpawnWritesMetaJSON asserts the durability contract: every17 // TestSpawnWritesMetaJSON asserts the durability contract: every
18 // successful Spawn leaves a meta.json on disk with the agent's18 // successful Spawn leaves a meta.json on disk with the agent's
19-// identity bits. Without this, Reload on the next zot launch can't19+// identity bits. Without this, Reload on the next zut launch can't
20 // find the agent and the user loses access to the worktree.20 // find the agent and the user loses access to the worktree.
21 func TestSpawnWritesMetaJSON(t *testing.T) {21 func TestSpawnWritesMetaJSON(t *testing.T) {
22 root := t.TempDir()22 root := t.TempDir()
@@ -63,7 +63,7 @@ func TestSpawnWritesMetaJSON(t *testing.T) {
63 }63 }
64 }64 }
65 65
66-// TestReloadRebuildsDetachedAgents simulates a zot restart by spawning66+// TestReloadRebuildsDetachedAgents simulates a zut restart by spawning
67 // in one Supervisor, throwing it away, then opening a fresh Supervisor against67 // in one Supervisor, throwing it away, then opening a fresh Supervisor against
68 // the same root and calling Reload. The user-visible state — id,68 // the same root and calling Reload. The user-visible state — id,
69 // task, branch, dir — must come back, and status must be Detached so69 // task, branch, dir — must come back, and status must be Detached so
@@ -873,7 +873,7 @@ func TestSpawnReqPersistsModel(t *testing.T) {
873 // updating this assertion is the failure mode this catches.873 // updating this assertion is the failure mode this catches.
874 func TestSubagentWorkerArgsIncludesModelFlags(t *testing.T) {874 func TestSubagentWorkerArgsIncludesModelFlags(t *testing.T) {
875 args := subagentWorkerArgs(subagentWorkerArgsOpts{875 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",
877 Task: "do x", Model: "gpt-5", Provider: "openai",877 Task: "do x", Model: "gpt-5", Provider: "openai",
878 })878 })
879 want := []string{"--model", "gpt-5", "--provider", "openai"}879 want := []string{"--model", "gpt-5", "--provider", "openai"}
@@ -1089,7 +1089,7 @@ func TestActiveSessionScopesSnapshotAll(t *testing.T) {
1089 // Supervisor instance is still SessionID="sess-A" after a fresh New +1089 // Supervisor instance is still SessionID="sess-A" after a fresh New +
1090 // Reload reads it back from meta.json. Without persistence, the1090 // Reload reads it back from meta.json. Without persistence, the
1091 // scope filter would forget which session owned each agent after1091 // 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.
1093 func TestSessionIDPersistsAcrossReload(t *testing.T) {1093 func TestSessionIDPersistsAcrossReload(t *testing.T) {
1094 root := t.TempDir()1094 root := t.TempDir()
1095 mkSupervisor := func() *Supervisor {1095 mkSupervisor := func() *Supervisor {
@@ -1134,7 +1134,7 @@ func TestSessionIDPersistsAcrossReload(t *testing.T) {
1134 // were spawned without an active session, e.g. via a test rig or1134 // were spawned without an active session, e.g. via a test rig or
1135 // scripted caller) carry SessionID == "" and remain visible from1135 // scripted caller) carry SessionID == "" and remain visible from
1136 // every scope. Otherwise the schema bump would orphan every1136 // 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.
1138 func TestEmptySessionIDIsVisibleFromAnyScope(t *testing.T) {1138 func TestEmptySessionIDIsVisibleFromAnyScope(t *testing.T) {
1139 root := t.TempDir()1139 root := t.TempDir()
1140 f := New(Config{1140 f := New(Config{
M packages/agent/subagents/process_unix.go +1 -1
@@ -39,7 +39,7 @@ func closeWorkerPipes(pipes []io.Closer) {
39 }39 }
40 40
41 // setProcessGroup puts a worker in a process group of its own. This keeps a41 // 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.
43 func setProcessGroup(cmd *exec.Cmd) {43 func setProcessGroup(cmd *exec.Cmd) {
44 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}44 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
45 }45 }
M packages/agent/subagents/profiles.go +3 -3
@@ -1,5 +1,5 @@
1 // Package subagents discovers named agent profiles that can be selected by1 // 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:
3 // a YAML-like metadata block followed by the agent's system prompt.3 // a YAML-like metadata block followed by the agent's system prompt.
4 //4 //
5 // Discovery prefers explicitly configured directories, then the shared5 // Discovery prefers explicitly configured directories, then the shared
@@ -180,7 +180,7 @@ func searchDirs(cwd, userHome string) []location {
180 out = append(out, location{dir: dir, label: label})180 out = append(out, location{dir: dir, label: label})
181 }181 }
182 }182 }
183- if extra := os.Getenv("ZOT_AGENT_PROFILES"); extra != "" {183+ if extra := os.Getenv("ZUT_AGENT_PROFILES"); extra != "" {
184 for _, dir := range filepath.SplitList(extra) {184 for _, dir := range filepath.SplitList(extra) {
185 dir = strings.TrimSpace(dir)185 dir = strings.TrimSpace(dir)
186 if dir != "" && !filepath.IsAbs(dir) && strings.TrimSpace(cwd) != "" {186 if dir != "" && !filepath.IsAbs(dir) && strings.TrimSpace(cwd) != "" {
@@ -192,7 +192,7 @@ func searchDirs(cwd, userHome string) []location {
192 if userHome != "" {192 if userHome != "" {
193 add(filepath.Join(userHome, ".agents", "agents"), "global (agents)")193 add(filepath.Join(userHome, ".agents", "agents"), "global (agents)")
194 // This is an additional compatibility location. The shared .agents194 // 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.
196 add(filepath.Join(userHome, ".pi", "agent", "agents"), "global (pi)")196 add(filepath.Join(userHome, ".pi", "agent", "agents"), "global (pi)")
197 }197 }
198 return out198 return out
M packages/agent/subagents/profiles_test.go +2 -2
@@ -79,7 +79,7 @@ Implement the requested change.
79 func TestDiscoverResolvesConfiguredRelativePathAgainstCWD(t *testing.T) {79 func TestDiscoverResolvesConfiguredRelativePathAgainstCWD(t *testing.T) {
80 first := t.TempDir()80 first := t.TempDir()
81 second := t.TempDir()81 second := t.TempDir()
82- t.Setenv("ZOT_AGENT_PROFILES", ".agents")82+ t.Setenv("ZUT_AGENT_PROFILES", ".agents")
83 writeProfile(t, filepath.Join(first, ".agents", "reviewer.md"), `---83 writeProfile(t, filepath.Join(first, ".agents", "reviewer.md"), `---
84 name: reviewer84 name: reviewer
85 description: First session profile85 description: First session profile
@@ -113,7 +113,7 @@ Second.
113 func TestDiscoverConfiguredDirectoryWinsAndRejectsUnsafeNames(t *testing.T) {113 func TestDiscoverConfiguredDirectoryWinsAndRejectsUnsafeNames(t *testing.T) {
114 configured := t.TempDir()114 configured := t.TempDir()
115 home := t.TempDir()115 home := t.TempDir()
116- t.Setenv("ZOT_AGENT_PROFILES", configured)116+ t.Setenv("ZUT_AGENT_PROFILES", configured)
117 writeProfile(t, filepath.Join(configured, "reviewer.md"), `---117 writeProfile(t, filepath.Join(configured, "reviewer.md"), `---
118 name: reviewer118 name: reviewer
119 description: Configured reviewer119 description: Configured reviewer
M packages/agent/subagents/runner.go +18 -18
@@ -14,11 +14,11 @@ import (
14 "time"14 "time"
15 )15 )
16 16
17-// execRunner spawns `zot --subagent-worker <inbox> --session <path>` in17+// execRunner spawns `zut --subagent-worker <inbox> --session <path>` in
18 // Agent.Dir (the shared repository root or an isolated worktree) and consumes18 // Agent.Dir (the shared repository root or an isolated worktree) and consumes
19 // its JSONL event stream on stdout.19 // its JSONL event stream on stdout.
20 //20 //
21-// Why a long-lived daemon and not `zot --print`: the supervisor and21+// Why a long-lived daemon and not `zut --print`: the supervisor and
22 // the user expect agents to keep accepting follow-up prompts. A22 // the user expect agents to keep accepting follow-up prompts. A
23 // one-shot subprocess can't do that; this design gives each subagent23 // one-shot subprocess can't do that; this design gives each subagent
24 // agent a persistent session file plus an inbox socket the parent24 // agent a persistent session file plus an inbox socket the parent
@@ -31,13 +31,13 @@ import (
31 //31 //
32 // The on-disk log is the durable record. The Sink updates are an32 // The on-disk log is the durable record. The Sink updates are an
33 // in-memory mirror so the dashboard doesn't have to tail the file33 // 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 would34+// for the parent's own agents. /subagents open in a separate zut would
35 // read the log directly.35 // read the log directly.
36 type execRunner struct {36 type execRunner struct {
37 agent *Agent37 agent *Agent
38 resolveCredential func(context.Context, string) (Credential, error)38 resolveCredential func(context.Context, string) (Credential, error)
39 39
40- // Command overrides the default `zot --subagent-worker ...`40+ // Command overrides the default `zut --subagent-worker ...`
41 // invocation. Tests set this to a fake binary (or `go run`41 // invocation. Tests set this to a fake binary (or `go run`
42 // against a tiny stub program) so the supervisor logic can be42 // against a tiny stub program) so the supervisor logic can be
43 // tested without a real child. Production code leaves it nil.43 // tested without a real child. Production code leaves it nil.
@@ -48,7 +48,7 @@ type execRunner struct {
48 // with <subagent-root>/agents/<id>/session.json. Tests that48 // with <subagent-root>/agents/<id>/session.json. Tests that
49 // hand-build an Agent without going through Spawn must set49 // hand-build an Agent without going through Spawn must set
50 // one of the two; the runner refuses to invent a fallback50 // 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)
52 // would litter the user's repo — every agent's Dir points52 // would litter the user's repo — every agent's Dir points
53 // at it directly.53 // at it directly.
54 SessionPath string54 SessionPath string
@@ -57,7 +57,7 @@ type execRunner struct {
57 // subagentWorkerArgsOpts captures every dynamic input to subagentWorkerArgs.57 // subagentWorkerArgsOpts captures every dynamic input to subagentWorkerArgs.
58 // The fields map 1:1 onto child CLI flags; empty values omit the flag58 // The fields map 1:1 onto child CLI flags; empty values omit the flag
59 // entirely and let the child resolve a default the same way a normal59 // entirely and let the child resolve a default the same way a normal
60-// `zot` invocation does.60+// `zut` invocation does.
61 type subagentWorkerArgsOpts struct {61 type subagentWorkerArgsOpts struct {
62 Exe string62 Exe string
63 Dir string63 Dir string
@@ -164,7 +164,7 @@ func subagentWorkerArgs(opts subagentWorkerArgsOpts) []string {
164 return args164 return args
165 }165 }
166 166
167-// resolveSubagentExecutable finds a runnable zot binary without assuming a167+// resolveSubagentExecutable finds a runnable zut binary without assuming a
168 // particular installation directory. A parent process can outlive the file168 // particular installation directory. A parent process can outlive the file
169 // it was started from (for example after a reinstall), so all known169 // it was started from (for example after a reinstall), so all known
170 // candidates are checked concurrently and the first successful lookup wins.170 // candidates are checked concurrently and the first successful lookup wins.
@@ -207,10 +207,10 @@ func resolveSubagentExecutable(ctx context.Context, self, argv0 string, lookPath
207 if argv0 != "" {207 if argv0 != "" {
208 addCandidate(filepath.Base(argv0))208 addCandidate(filepath.Base(argv0))
209 }209 }
210- addCandidate("zot")210+ addCandidate("zut")
211 211
212 if len(candidates) == 0 {212 if len(candidates) == 0 {
213- return "", fmt.Errorf("locate zot executable: no executable candidates")213+ return "", fmt.Errorf("locate zut executable: no executable candidates")
214 }214 }
215 215
216 type lookupResult struct {216 type lookupResult struct {
@@ -240,7 +240,7 @@ func resolveSubagentExecutable(ctx context.Context, self, argv0 string, lookPath
240 }240 }
241 lookupErrors = append(lookupErrors, fmt.Sprintf("%q: %v", result.candidate, result.err))241 lookupErrors = append(lookupErrors, fmt.Sprintf("%q: %v", result.candidate, result.err))
242 }242 }
243- return "", fmt.Errorf("locate zot executable: %s", strings.Join(lookupErrors, "; "))243+ return "", fmt.Errorf("locate zut executable: %s", strings.Join(lookupErrors, "; "))
244 }244 }
245 245
246 func (r *execRunner) Run(ctx context.Context, sink Sink) error {246 func (r *execRunner) Run(ctx context.Context, sink Sink) error {
@@ -251,7 +251,7 @@ func (r *execRunner) Run(ctx context.Context, sink Sink) error {
251 // <subagent-root>/agents/<id>/session.json so the per-251 // <subagent-root>/agents/<id>/session.json so the per-
252 // agent state is entirely outside the working tree.252 // agent state is entirely outside the working tree.
253 // Crucial because Agent.Dir points at the user's repo;253 // Crucial because Agent.Dir points at the user's repo;
254- // any .zot/ scratch directory under Dir would litter254+ // any .zut/ scratch directory under Dir would litter
255 // their source tree.255 // their source tree.
256 //256 //
257 // There is no third fallback. If neither path is set we257 // 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 {
291 exe, err := resolveSubagentExecutable(ctx, self, argv0, nil)291 exe, err := resolveSubagentExecutable(ctx, self, argv0, nil)
292 if err != nil {292 if err != nil {
293 if selfErr != nil {293 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)
295 }295 }
296 return err296 return err
297 }297 }
@@ -301,17 +301,17 @@ func (r *execRunner) Run(ctx context.Context, sink Sink) error {
301 cmd := exec.CommandContext(ctx, args[0], args[1:]...)301 cmd := exec.CommandContext(ctx, args[0], args[1:]...)
302 cmd.Dir = r.agent.Dir302 cmd.Dir = r.agent.Dir
303 cmd.Env = append(workerEnvironment(r.agent.Provider),303 cmd.Env = append(workerEnvironment(r.agent.Provider),
304- "ZOT_SUBAGENT_AGENT_ID="+r.agent.ID,304+ "ZUT_SUBAGENT_AGENT_ID="+r.agent.ID,
305- "ZOT_SUBAGENT_EVENT_LOG="+logPath,305+ "ZUT_SUBAGENT_EVENT_LOG="+logPath,
306 )306 )
307 if r.agent.HeartbeatInterval > 0 {307 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())
309 }309 }
310 if r.agent.maxOutputBytes > 0 {310 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))
312 }312 }
313 if r.agent.maxOutputLines > 0 {313 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))
315 }315 }
316 if r.resolveCredential != nil {316 if r.resolveCredential != nil {
317 credential, resolveErr := r.resolveCredential(ctx, r.agent.Provider)317 credential, resolveErr := r.resolveCredential(ctx, r.agent.Provider)
@@ -324,7 +324,7 @@ func (r *execRunner) Run(ctx context.Context, sink Sink) error {
324 return fmt.Errorf("encode subagent credential: %w", encodeErr)324 return fmt.Errorf("encode subagent credential: %w", encodeErr)
325 }325 }
326 cmd.Stdin = bytes.NewReader(encoded)326 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")
328 }328 }
329 }329 }
330 330
M packages/agent/subagents/runner_e2e_test.go +1 -1
@@ -40,7 +40,7 @@ func TestRunnerEndToEndWithStubChild(t *testing.T) {
40 }40 }
41 41
42 exe := buildStubChild(t)42 exe := buildStubChild(t)
43- t.Setenv("ZOT_STUB_PROTOCOL", "1")43+ t.Setenv("ZUT_STUB_PROTOCOL", "1")
44 44
45 root := t.TempDir()45 root := t.TempDir()
46 repo := t.TempDir()46 repo := t.TempDir()
M packages/agent/subagents/runner_test.go +25 -25
@@ -20,7 +20,7 @@ import (
20 // talk to the model.20 // talk to the model.
21 //21 //
22 // - Forgetting --cwd: the child resolved tools against the parent22 // - Forgetting --cwd: the child resolved tools against the parent
23-// zot's working directory, defeating the whole point of the23+// zut's working directory, defeating the whole point of the
24 // worktree isolation.24 // worktree isolation.
25 //25 //
26 // - Forgetting --session: a daemon-mode agent without a session26 // - Forgetting --session: a daemon-mode agent without a session
@@ -31,10 +31,10 @@ import (
31 // positions. If a flag is renamed, update both the runner and this31 // positions. If a flag is renamed, update both the runner and this
32 // test so we notice immediately.32 // test so we notice immediately.
33 func TestCredentialStdinHelperProcess(t *testing.T) {33 func TestCredentialStdinHelperProcess(t *testing.T) {
34- if os.Getenv("ZOT_SUBAGENT_CREDENTIAL_HELPER") != "1" {34+ if os.Getenv("ZUT_SUBAGENT_CREDENTIAL_HELPER") != "1" {
35 return35 return
36 }36 }
37- if os.Getenv("ZOT_SUBAGENT_CREDENTIAL_STDIN") != "1" {37+ if os.Getenv("ZUT_SUBAGENT_CREDENTIAL_STDIN") != "1" {
38 os.Exit(2)38 os.Exit(2)
39 }39 }
40 var credential Credential40 var credential Credential
@@ -67,7 +67,7 @@ func TestWorkerEnvironmentRedactsProviderSecrets(t *testing.T) {
67 t.Setenv("CUSTOM_PROVIDER_KEY", "key-secret")67 t.Setenv("CUSTOM_PROVIDER_KEY", "key-secret")
68 t.Setenv("AWS_ACCESS_KEY_ID", "aws-access-secret")68 t.Setenv("AWS_ACCESS_KEY_ID", "aws-access-secret")
69 t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/service-account.json")69 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")
71 env := strings.Join(workerEnvironment("openai"), "\n")71 env := strings.Join(workerEnvironment("openai"), "\n")
72 for _, secret := range []string{72 for _, secret := range []string{
73 "OPENAI_API_KEY=openai-secret",73 "OPENAI_API_KEY=openai-secret",
@@ -83,7 +83,7 @@ func TestWorkerEnvironmentRedactsProviderSecrets(t *testing.T) {
83 t.Fatalf("worker environment leaked %s", secret)83 t.Fatalf("worker environment leaked %s", secret)
84 }84 }
85 }85 }
86- if !strings.Contains(env, "ZOT_SUBAGENT_TEST_VALUE=not-secret") {86+ if !strings.Contains(env, "ZUT_SUBAGENT_TEST_VALUE=not-secret") {
87 t.Fatal("worker environment dropped unrelated variables")87 t.Fatal("worker environment dropped unrelated variables")
88 }88 }
89 89
@@ -98,7 +98,7 @@ func TestWorkerEnvironmentRedactsProviderSecrets(t *testing.T) {
98 }98 }
99 99
100 func TestExecRunnerTransfersCredentialOnlyOnStdin(t *testing.T) {100 func TestExecRunnerTransfersCredentialOnlyOnStdin(t *testing.T) {
101- t.Setenv("ZOT_SUBAGENT_CREDENTIAL_HELPER", "1")101+ t.Setenv("ZUT_SUBAGENT_CREDENTIAL_HELPER", "1")
102 root := t.TempDir()102 root := t.TempDir()
103 a := &Agent{103 a := &Agent{
104 ID: "credential-test",104 ID: "credential-test",
@@ -147,7 +147,7 @@ func TestApplyEventPreservesMultilineRolesAndFinalAssistant(t *testing.T) {
147 147
148 func TestSubagentWorkerArgs(t *testing.T) {148 func TestSubagentWorkerArgs(t *testing.T) {
149 args := subagentWorkerArgs(subagentWorkerArgsOpts{149 args := subagentWorkerArgs(subagentWorkerArgsOpts{
150- Exe: "/path/to/zot",150+ Exe: "/path/to/zut",
151 Dir: "/tmp/worktree",151 Dir: "/tmp/worktree",
152 SessionPath: "/tmp/state/session.json",152 SessionPath: "/tmp/state/session.json",
153 InboxPath: "/tmp/state/in.sock",153 InboxPath: "/tmp/state/in.sock",
@@ -158,7 +158,7 @@ func TestSubagentWorkerArgs(t *testing.T) {
158 if len(args) < 7 {158 if len(args) < 7 {
159 t.Fatalf("argv unexpectedly short: %v", args)159 t.Fatalf("argv unexpectedly short: %v", args)
160 }160 }
161- if args[0] != "/path/to/zot" {161+ if args[0] != "/path/to/zut" {
162 t.Fatalf("argv[0] = %q; want the binary path", args[0])162 t.Fatalf("argv[0] = %q; want the binary path", args[0])
163 }163 }
164 // The task must come last so anything that looks flag-like in164 // The task must come last so anything that looks flag-like in
@@ -195,8 +195,8 @@ func TestSubagentWorkerArgs(t *testing.T) {
195 }195 }
196 196
197 func TestResolveSubagentExecutableUsesCurrentWhenAvailable(t *testing.T) {197 func TestResolveSubagentExecutableUsesCurrentWhenAvailable(t *testing.T) {
198- got, err := resolveSubagentExecutable(context.Background(), "/current/bin/zot", "/old/bin/zot", func(_ context.Context, candidate string) (string, error) {198+ got, err := resolveSubagentExecutable(context.Background(), "/current/bin/zut", "/old/bin/zut", func(_ context.Context, candidate string) (string, error) {
199- if candidate == "/current/bin/zot" {199+ if candidate == "/current/bin/zut" {
200 return candidate, nil200 return candidate, nil
201 }201 }
202 return "", os.ErrNotExist202 return "", os.ErrNotExist
@@ -204,22 +204,22 @@ func TestResolveSubagentExecutableUsesCurrentWhenAvailable(t *testing.T) {
204 if err != nil {204 if err != nil {
205 t.Fatal(err)205 t.Fatal(err)
206 }206 }
207- if got != "/current/bin/zot" {207+ if got != "/current/bin/zut" {
208 t.Fatalf("executable = %q, want current executable", got)208 t.Fatalf("executable = %q, want current executable", got)
209 }209 }
210 }210 }
211 211
212 func TestResolveSubagentExecutableFallsBackToPath(t *testing.T) {212 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) {213+ got, err := resolveSubagentExecutable(context.Background(), "/removed/.local/bin/zut", "/removed/.local/bin/zut", func(_ context.Context, candidate string) (string, error) {
214- if candidate == "zot" {214+ if candidate == "zut" {
215- return "/active/go/bin/zot", nil215+ return "/active/go/bin/zut", nil
216 }216 }
217 return "", os.ErrNotExist217 return "", os.ErrNotExist
218 })218 })
219 if err != nil {219 if err != nil {
220 t.Fatal(err)220 t.Fatal(err)
221 }221 }
222- if got != "/active/go/bin/zot" {222+ if got != "/active/go/bin/zut" {
223 t.Fatalf("executable = %q, want PATH fallback", got)223 t.Fatalf("executable = %q, want PATH fallback", got)
224 }224 }
225 }225 }
@@ -227,16 +227,16 @@ func TestResolveSubagentExecutableFallsBackToPath(t *testing.T) {
227 func TestResolveSubagentExecutableCancelsOtherLookupsAfterFirstSuccess(t *testing.T) {227 func TestResolveSubagentExecutableCancelsOtherLookupsAfterFirstSuccess(t *testing.T) {
228 slowStarted := make(chan struct{})228 slowStarted := make(chan struct{})
229 slowCanceled := make(chan struct{})229 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) {
231 switch candidate {231 switch candidate {
232- case "/current/bin/zot":232+ case "/current/bin/zut":
233 close(slowStarted)233 close(slowStarted)
234 <-ctx.Done()234 <-ctx.Done()
235 close(slowCanceled)235 close(slowCanceled)
236 return "", ctx.Err()236 return "", ctx.Err()
237- case "zot":237+ case "zut":
238 <-slowStarted238 <-slowStarted
239- return "/active/go/bin/zot", nil239+ return "/active/go/bin/zut", nil
240 default:240 default:
241 return "", os.ErrNotExist241 return "", os.ErrNotExist
242 }242 }
@@ -244,7 +244,7 @@ func TestResolveSubagentExecutableCancelsOtherLookupsAfterFirstSuccess(t *testin
244 if err != nil {244 if err != nil {
245 t.Fatal(err)245 t.Fatal(err)
246 }246 }
247- if got != "/active/go/bin/zot" {247+ if got != "/active/go/bin/zut" {
248 t.Fatalf("executable = %q, want first successful lookup", got)248 t.Fatalf("executable = %q, want first successful lookup", got)
249 }249 }
250 select {250 select {
@@ -256,7 +256,7 @@ func TestResolveSubagentExecutableCancelsOtherLookupsAfterFirstSuccess(t *testin
256 256
257 func TestSubagentWorkerArgsPropagatesProviderConnectionSettings(t *testing.T) {257 func TestSubagentWorkerArgsPropagatesProviderConnectionSettings(t *testing.T) {
258 args := subagentWorkerArgs(subagentWorkerArgsOpts{258 args := subagentWorkerArgs(subagentWorkerArgsOpts{
259- Exe: "/zot",259+ Exe: "/zut",
260 Dir: "/work",260 Dir: "/work",
261 SessionPath: "/state/session.json",261 SessionPath: "/state/session.json",
262 InboxPath: "/state/inbox.sock",262 InboxPath: "/state/inbox.sock",
@@ -282,7 +282,7 @@ func TestDefaultChildArgsPropagatesProviderConnectionSettings(t *testing.T) {
282 BaseURL: "https://gateway.example.test/v1",282 BaseURL: "https://gateway.example.test/v1",
283 InsecureTLS: true,283 InsecureTLS: true,
284 }284 }
285- args := defaultChildArgs("/zot", a, "/state/session.json", "/state/inbox.sock")285+ args := defaultChildArgs("/zut", a, "/state/session.json", "/state/inbox.sock")
286 if i := indexOf(args, "--base-url"); i < 0 || safeAt(args, i+1) != a.BaseURL {286 if i := indexOf(args, "--base-url"); i < 0 || safeAt(args, i+1) != a.BaseURL {
287 t.Fatalf("argv = %v, want Agent.BaseURL propagated", args)287 t.Fatalf("argv = %v, want Agent.BaseURL propagated", args)
288 }288 }
@@ -296,7 +296,7 @@ func TestDefaultChildArgsPropagatesProviderConnectionSettings(t *testing.T) {
296 // positional which the arg parser would treat as a real prompt.296 // positional which the arg parser would treat as a real prompt.
297 func TestSubagentWorkerArgsEmptyTaskOmitsPositional(t *testing.T) {297 func TestSubagentWorkerArgsEmptyTaskOmitsPositional(t *testing.T) {
298 args := subagentWorkerArgs(subagentWorkerArgsOpts{298 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",
300 })300 })
301 for _, a := range args {301 for _, a := range args {
302 if a == "" {302 if a == "" {
@@ -320,7 +320,7 @@ func TestSubagentWorkerArgsEmptyTaskOmitsPositional(t *testing.T) {
320 // initial user turn.320 // initial user turn.
321 func TestDefaultChildArgsSpawnIncludesTask(t *testing.T) {321 func TestDefaultChildArgsSpawnIncludesTask(t *testing.T) {
322 a := &Agent{Dir: "/wt", Task: "do thing"}322 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")
324 if got := args[len(args)-1]; got != "do thing" {324 if got := args[len(args)-1]; got != "do thing" {
325 t.Fatalf("spawn argv last = %q; want %q\n%v", got, "do thing", args)325 t.Fatalf("spawn argv last = %q; want %q\n%v", got, "do thing", args)
326 }326 }
@@ -334,7 +334,7 @@ func TestDefaultChildArgsSpawnIncludesTask(t *testing.T) {
334 // user types next via the inbox.334 // user types next via the inbox.
335 func TestDefaultChildArgsResumeOmitsTask(t *testing.T) {335 func TestDefaultChildArgsResumeOmitsTask(t *testing.T) {
336 a := &Agent{Dir: "/wt", Task: "do thing", Resuming: true}336 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")
338 for _, v := range args {338 for _, v := range args {
339 if v == "do thing" {339 if v == "do thing" {
340 t.Fatalf("resume argv contains the task; it would re-fire as a duplicate turn\n%v", args)340 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
29 // 3. /tmp as a Unix fallback.29 // 3. /tmp as a Unix fallback.
30 //30 //
31 // Each candidate is probed with a real unix listener before it is selected.31 // 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 a32+// The root hash keeps separate zut homes from colliding while preserving a
33 // stable path across parent and child processes and across Resume calls.33 // stable path across parent and child processes and across Resume calls.
34 func inboxSocketPath(root, agentID string) (string, error) {34 func inboxSocketPath(root, agentID string) (string, error) {
35 if runtime.GOOS == "windows" {35 if runtime.GOOS == "windows" {
@@ -65,7 +65,7 @@ func inboxSocketPath(root, agentID string) (string, error) {
65 }65 }
66 66
67 func socketPathInBase(base, root, agentID string) (string, error) {67 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))
69 if err := os.MkdirAll(dir, 0o700); err != nil {69 if err := os.MkdirAll(dir, 0o700); err != nil {
70 return "", fmt.Errorf("socket dir %s: %w", dir, err)70 return "", fmt.Errorf("socket dir %s: %w", dir, err)
71 }71 }
@@ -110,7 +110,7 @@ func probeUnixSocket(dir string) error {
110 }110 }
111 111
112 // rootTag returns a stable 8-hex-char tag for the subagent root. Used112 // rootTag returns a stable 8-hex-char tag for the subagent root. Used
113-// in the runtime-directory name so two parallel zot instances with113+// in the runtime-directory name so two parallel zut instances with
114 // different roots don't share sockets.114 // different roots don't share sockets.
115 func rootTag(root string) string { return shortHash(root) }115 func rootTag(root string) string { return shortHash(root) }
116 116
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.
2 //2 //
3-// A Supervisor manages headless, long-lived zot subprocesses and keeps their3+// A Supervisor manages headless, long-lived zut subprocesses and keeps their
4 // process/turn state, event logs, sessions, results, and optional worktree4 // process/turn state, event logs, sessions, results, and optional worktree
5 // artifacts durable across supervisor restarts.5 // artifacts durable across supervisor restarts.
6 //6 //
7 // Shared workspaces preserve the historical behavior, while an explicit7 // Shared workspaces preserve the historical behavior, while an explicit
8 // worktree request gives a child an isolated checkout whose patch is captured8 // worktree request gives a child an isolated checkout whose patch is captured
9 // before cleanup. The Runner abstraction lets tests inject a fake process;9 // before cleanup. The Runner abstraction lets tests inject a fake process;
10-// production uses `zot --subagent-worker ...`.10+// production uses `zut --subagent-worker ...`.
11 package subagents11 package subagents
12 12
13 import (13 import (
@@ -43,7 +43,7 @@ type Config struct {
43 Context context.Context43 Context context.Context
44 44
45 // Root is the directory under which per-agent state files live.45 // 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.
47 Root string47 Root string
48 48
49 // Policy owns concurrency, timeout, output, and path safety limits.49 // Policy owns concurrency, timeout, output, and path safety limits.
@@ -70,7 +70,7 @@ type Config struct {
70 InsecureTLS bool70 InsecureTLS bool
71 71
72 // NewRunner produces the Runner for an Agent. If nil, the default72 // NewRunner produces the Runner for an Agent. If nil, the default
73- // `zot --subagent-worker ...` exec runner is used. Tests inject a fake73+ // `zut --subagent-worker ...` exec runner is used. Tests inject a fake
74 // here.74 // here.
75 NewRunner func(a *Agent) Runner75 NewRunner func(a *Agent) Runner
76 76
@@ -254,7 +254,7 @@ func (f *Supervisor) ProviderSettings() (string, bool) {
254 }254 }
255 255
256 // SetActiveSession scopes the dashboard view (and Spawn stamping)256 // SetActiveSession scopes the dashboard view (and Spawn stamping)
257-// to a particular host zot session id. Pass empty to clear the257+// to a particular host zut session id. Pass empty to clear the
258 // scope and revert to "show every agent" (the original behaviour).258 // scope and revert to "show every agent" (the original behaviour).
259 //259 //
260 // Existing in-memory agents keep their SessionID; only the filter260 // 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
422 logPath := filepath.Join(stateDir, "events.jsonl")422 logPath := filepath.Join(stateDir, "events.jsonl")
423 sessionPath := filepath.Join(stateDir, "session.json")423 sessionPath := filepath.Join(stateDir, "session.json")
424 // Keep the transient unix socket outside the durable state root.424 // Keep the transient unix socket outside the durable state root.
425- // ZOT_HOME may live on a shared or network filesystem that supports425+ // ZUT_HOME may live on a shared or network filesystem that supports
426 // regular state files but cannot host unix socket nodes.426 // regular state files but cannot host unix socket nodes.
427 inboxPath, err := inboxSocketPath(f.cfg.Root, id)427 inboxPath, err := inboxSocketPath(f.cfg.Root, id)
428 if err != nil {428 if err != nil {
M packages/agent/subagents/supervisor_test.go +1 -1
@@ -63,7 +63,7 @@ func TestSpawnRunsAndCompletes(t *testing.T) {
63 }63 }
64 64
65 // TestSpawnAgentSharesRepoRoot verifies the only-mode-we-support:65 // 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.
67 func TestSpawnAgentSharesRepoRoot(t *testing.T) {67 func TestSpawnAgentSharesRepoRoot(t *testing.T) {
68 f := newTestSupervisor(t, func(a *Agent) Runner {68 f := newTestSupervisor(t, func(a *Agent) Runner {
69 return RunnerFunc(func(ctx context.Context, sink Sink) error { return nil })69 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 @@
1 // stubchild is a minimal subagent-worker stand-in used by the runner1 // stubchild is a minimal subagent-worker stand-in used by the runner
2-// end-to-end test. It speaks the daemon protocol the real zot2+// end-to-end test. It speaks the daemon protocol the real zut
3 // binary will implement next:3 // binary will implement next:
4 //4 //
5 // - parses --subagent-worker <path>, --session <path>, --cwd <path>,5 // - parses --subagent-worker <path>, --session <path>, --cwd <path>,
@@ -14,7 +14,7 @@
14 //14 //
15 // The runner test compiles this binary into a tempdir, points15 // The runner test compiles this binary into a tempdir, points
16 // subagent's execRunner at it via Command, and asserts the events16 // subagent's execRunner at it via Command, and asserts the events
17-// flow through correctly without needing the full zot model17+// flow through correctly without needing the full zut model
18 // machinery.18 // machinery.
19 package main19 package main
20 20
@@ -28,7 +28,7 @@ import (
28 "sync"28 "sync"
29 "time"29 "time"
30 30
31- "github.com/patriceckhart/zot/packages/agent/subagents"31+ "github.com/bnema/zut/packages/agent/subagents"
32 )32 )
33 33
34 func main() {34 func main() {
@@ -134,7 +134,7 @@ type emitter = func(string, map[string]any)
134 func newEmitter() emitter {134 func newEmitter() emitter {
135 var mu sync.Mutex135 var mu sync.Mutex
136 enc := json.NewEncoder(os.Stdout)136 enc := json.NewEncoder(os.Stdout)
137- versioned := os.Getenv("ZOT_STUB_PROTOCOL") == "1"137+ versioned := os.Getenv("ZUT_STUB_PROTOCOL") == "1"
138 return func(typ string, data map[string]any) {138 return func(typ string, data map[string]any) {
139 mu.Lock()139 mu.Lock()
140 defer mu.Unlock()140 defer mu.Unlock()
M packages/agent/systemprompt.go +8 -8
@@ -24,7 +24,7 @@ type SystemPromptOpts struct {
24 Custom string // if set, replaces the default identity entirely24 Custom string // if set, replaces the default identity entirely
25 Append []string // extra text appended at the end25 Append []string // extra text appended at the end
26 Now time.Time26 Now time.Time
27- ZotDocsDir string27+ ZutDocsDir string
28 }28 }
29 29
30 // BuildSystemPrompt constructs the system prompt.30 // BuildSystemPrompt constructs the system prompt.
@@ -33,7 +33,7 @@ type SystemPromptOpts struct {
33 // the cached prefix on every request, so bloat is cumulatively33 // the cached prefix on every request, so bloat is cumulatively
34 // expensive. We ship only:34 // expensive. We ship only:
35 //35 //
36-// - A one-paragraph identity (who zot is, what the name means,36+// - A one-paragraph identity (who zut is, what the name means,
37 // what the TUI expects for output format).37 // what the TUI expects for output format).
38 // - The date + cwd footer so the model has current-context.38 // - The date + cwd footer so the model has current-context.
39 //39 //
@@ -45,7 +45,7 @@ type SystemPromptOpts struct {
45 //45 //
46 // Users who want extra biasing can use --system-prompt (replace),46 // Users who want extra biasing can use --system-prompt (replace),
47 // --append-system-prompt (additive, repeatable), or drop a47 // --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.
49 func BuildSystemPrompt(o SystemPromptOpts) string {49 func BuildSystemPrompt(o SystemPromptOpts) string {
50 if o.Now.IsZero() {50 if o.Now.IsZero() {
51 o.Now = time.Now()51 o.Now = time.Now()
@@ -64,10 +64,10 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
64 sb.WriteString(defaultIdentity)64 sb.WriteString(defaultIdentity)
65 }65 }
66 66
67- if strings.TrimSpace(o.ZotDocsDir) != "" {67+ if strings.TrimSpace(o.ZutDocsDir) != "" {
68- sb.WriteString("\n\nZot's own docs are installed under ")68+ sb.WriteString("\n\nZut's own docs are installed under ")
69- sb.WriteString(o.ZotDocsDir)69+ sb.WriteString(o.ZutDocsDir)
70- sb.WriteString("; use the read tool there when you need details about zot RPC, extensions, skills, or built-in behaviour.")70+ sb.WriteString("; use the read tool there when you need details about zut RPC, extensions, skills, or built-in behaviour.")
71 }71 }
72 72
73 for _, a := range o.Append {73 for _, a := range o.Append {
@@ -82,7 +82,7 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
82 return sb.String()82 return sb.String()
83 }83 }
84 84
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.
86 86
87 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.87 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.
88 88
M packages/agent/tools/bash.go +3 -3
@@ -15,8 +15,8 @@ import (
15 "strings"15 "strings"
16 "time"16 "time"
17 17
18- "github.com/patriceckhart/zot/packages/core"18+ "github.com/bnema/zut/packages/core"
19- "github.com/patriceckhart/zot/packages/provider"19+ "github.com/bnema/zut/packages/provider"
20 )20 )
21 21
22 const (22 const (
@@ -224,7 +224,7 @@ func humanDuration(d time.Duration) string {
224 func writeFullOutput(s string) string {224 func writeFullOutput(s string) string {
225 b := make([]byte, 6)225 b := make([]byte, 6)
226 _, _ = rand.Read(b)226 _, _ = 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")
228 if err := os.WriteFile(name, []byte(s), 0o600); err != nil {228 if err := os.WriteFile(name, []byte(s), 0o600); err != nil {
229 return ""229 return ""
230 }230 }
M packages/agent/tools/edit.go +3 -3
@@ -8,9 +8,9 @@ import (
8 "os"8 "os"
9 "strings"9 "strings"
10 10
11- "github.com/patriceckhart/zot/packages/agent/lsp"11+ "github.com/bnema/zut/packages/agent/lsp"
12- "github.com/patriceckhart/zot/packages/core"12+ "github.com/bnema/zut/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 // EditTool applies one or more exact-match replacements to a file.16 // EditTool applies one or more exact-match replacements to a file.
M packages/agent/tools/lsp.go +3 -3
@@ -10,9 +10,9 @@ import (
10 "time"10 "time"
11 "unicode/utf8"11 "unicode/utf8"
12 12
13- "github.com/patriceckhart/zot/packages/agent/lsp"13+ "github.com/bnema/zut/packages/agent/lsp"
14- "github.com/patriceckhart/zot/packages/core"14+ "github.com/bnema/zut/packages/core"
15- "github.com/patriceckhart/zot/packages/provider"15+ "github.com/bnema/zut/packages/provider"
16 )16 )
17 17
18 const maxLSPToolOutput = 60 * 102418 const maxLSPToolOutput = 60 * 1024
M packages/agent/tools/lsp_diagnostics.go +3 -3
@@ -6,9 +6,9 @@ import (
6 "strings"6 "strings"
7 "time"7 "time"
8 8
9- "github.com/patriceckhart/zot/packages/agent/lsp"9+ "github.com/bnema/zut/packages/agent/lsp"
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12 )12 )
13 13
14 const writeDiagnosticsTimeout = 2 * time.Second14 const writeDiagnosticsTimeout = 2 * time.Second
M packages/agent/tools/lsp_test.go +2 -2
@@ -10,8 +10,8 @@ import (
10 "testing"10 "testing"
11 "unicode/utf8"11 "unicode/utf8"
12 12
13- "github.com/patriceckhart/zot/packages/agent/lsp"13+ "github.com/bnema/zut/packages/agent/lsp"
14- "github.com/patriceckhart/zot/packages/provider"14+ "github.com/bnema/zut/packages/provider"
15 )15 )
16 16
17 func TestLSPToolSchemaAndFormatting(t *testing.T) {17 func TestLSPToolSchemaAndFormatting(t *testing.T) {
M packages/agent/tools/permissions.go +1 -1
@@ -6,7 +6,7 @@ import (
6 "strings"6 "strings"
7 )7 )
8 8
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.
10 type PermissionSet struct {10 type PermissionSet struct {
11 FS struct {11 FS struct {
12 Read []string `json:"read"`12 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.
2 package tools2 package tools
3 3
4 import (4 import (
@@ -11,8 +11,8 @@ import (
11 "path/filepath"11 "path/filepath"
12 "strings"12 "strings"
13 13
14- "github.com/patriceckhart/zot/packages/core"14+ "github.com/bnema/zut/packages/core"
15- "github.com/patriceckhart/zot/packages/provider"15+ "github.com/bnema/zut/packages/provider"
16 )16 )
17 17
18 const (18 const (
@@ -195,7 +195,7 @@ func imageMIME(path string) string {
195 // returns the real media type, independent of the file's extension.195 // returns the real media type, independent of the file's extension.
196 // Providers validate the declared media type against the actual bytes196 // Providers validate the declared media type against the actual bytes
197 // and 400 the whole request on a mismatch, so the extension can never197 // 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 images198+// be trusted. Returns "" when the format is not one zut ships images
199 // for, leaving the caller's extension-based guess in place.199 // for, leaving the caller's extension-based guess in place.
200 func sniffImageMIME(data []byte) string {200 func sniffImageMIME(data []byte) string {
201 switch {201 switch {
M packages/agent/tools/subagent_spawn.go +3 -3
@@ -7,9 +7,9 @@ import (
7 "strings"7 "strings"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/agent/subagents"10+ "github.com/bnema/zut/packages/agent/subagents"
11- "github.com/patriceckhart/zot/packages/core"11+ "github.com/bnema/zut/packages/core"
12- "github.com/patriceckhart/zot/packages/provider"12+ "github.com/bnema/zut/packages/provider"
13 )13 )
14 14
15 // SubagentSpawnTool lets the main agent fork a background sub-agent15 // 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 (
8 "testing"8 "testing"
9 "time"9 "time"
10 10
11- "github.com/patriceckhart/zot/packages/agent/subagents"11+ "github.com/bnema/zut/packages/agent/subagents"
12- "github.com/patriceckhart/zot/packages/provider"12+ "github.com/bnema/zut/packages/provider"
13 )13 )
14 14
15 type noopSupervisorRunner struct{}15 type noopSupervisorRunner struct{}
M packages/agent/tools/subagent_status.go +3 -3
@@ -8,9 +8,9 @@ import (
8 "time"8 "time"
9 "unicode/utf8"9 "unicode/utf8"
10 10
11- "github.com/patriceckhart/zot/packages/agent/subagents"11+ "github.com/bnema/zut/packages/agent/subagents"
12- "github.com/patriceckhart/zot/packages/core"12+ "github.com/bnema/zut/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 // SubagentStatusTool reports live state for background sub-agents without16 // SubagentStatusTool reports live state for background sub-agents without
M packages/agent/tools/subagent_status_test.go +1 -1
@@ -9,7 +9,7 @@ import (
9 "testing"9 "testing"
10 "time"10 "time"
11 11
12- "github.com/patriceckhart/zot/packages/agent/subagents"12+ "github.com/bnema/zut/packages/agent/subagents"
13 )13 )
14 14
15 type statusTestRunner struct {15 type statusTestRunner struct {
M packages/agent/tools/telegram_send.go +2 -2
@@ -6,8 +6,8 @@ import (
6 "fmt"6 "fmt"
7 "os"7 "os"
8 8
9- "github.com/patriceckhart/zot/packages/core"9+ "github.com/bnema/zut/packages/core"
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 // TelegramSender is the small affordance the telegram-send tools call13 // TelegramSender is the small affordance the telegram-send tools call
M packages/agent/tools/tools_test.go +1 -1
@@ -10,7 +10,7 @@ import (
10 "strings"10 "strings"
11 "testing"11 "testing"
12 12
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 func mustJSON(t *testing.T, v any) json.RawMessage {16 func mustJSON(t *testing.T, v any) json.RawMessage {
M packages/agent/tools/write.go +3 -3
@@ -8,9 +8,9 @@ import (
8 "path/filepath"8 "path/filepath"
9 "strings"9 "strings"
10 10
11- "github.com/patriceckhart/zot/packages/agent/lsp"11+ "github.com/bnema/zut/packages/agent/lsp"
12- "github.com/patriceckhart/zot/packages/core"12+ "github.com/bnema/zut/packages/core"
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 // WriteTool writes content to a file, creating parent directories.16 // WriteTool writes content to a file, creating parent directories.
M packages/agent/update.go +8 -8
@@ -14,7 +14,7 @@ import (
14 14
15 // updateCheckTTL is how often we hit the GitHub API to look for a new15 // updateCheckTTL is how often we hit the GitHub API to look for a new
16 // release. Half a day is frequent enough to notice the same day a16 // 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.
18 const updateCheckTTL = 12 * time.Hour18 const updateCheckTTL = 12 * time.Hour
19 19
20 // updateCheckFile is the on-disk cache, keyed to the current binary20 // updateCheckFile is the on-disk cache, keyed to the current binary
@@ -24,7 +24,7 @@ const updateCheckFile = "update-check.json"
24 24
25 // githubReleasesAPI is the REST endpoint we query. Using the API (not25 // githubReleasesAPI is the REST endpoint we query. Using the API (not
26 // the HTML redirect) because the JSON response is stable and small.26 // 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"
28 28
29 const devVersionNotice = "dev version detected; skipping update check"29 const devVersionNotice = "dev version detected; skipping update check"
30 30
@@ -46,7 +46,7 @@ func isDevVersion(version string) bool {
46 version == "0.0.0-dev" || strings.HasPrefix(version, "0.0.0-dev+")46 version == "0.0.0-dev" || strings.HasPrefix(version, "0.0.0-dev+")
47 }47 }
48 48
49-// updateCache is the on-disk structure written to $ZOT_HOME.49+// updateCache is the on-disk structure written to $ZUT_HOME.
50 type updateCache struct {50 type updateCache struct {
51 CheckedAt time.Time `json:"checked_at"`51 CheckedAt time.Time `json:"checked_at"`
52 // The version that was current when we last checked. Invalidates52 // The version that was current when we last checked. Invalidates
@@ -63,14 +63,14 @@ type updateCache struct {
63 // Always returns a usable UpdateInfo (zero-value on error). The63 // Always returns a usable UpdateInfo (zero-value on error). The
64 // banner renderer skips the display when Available is false, so a64 // banner renderer skips the display when Available is false, so a
65 // network failure silently no-ops; we never block startup on this.65 // 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 {
67 // Development builds never have an update to offer. Skip without67 // Development builds never have an update to offer. Skip without
68 // reading the cache or touching the network.68 // reading the cache or touching the network.
69 if isDevVersion(currentVersion) {69 if isDevVersion(currentVersion) {
70 return UpdateInfo{}70 return UpdateInfo{}
71 }71 }
72 72
73- cachePath := filepath.Join(zotHome, updateCheckFile)73+ cachePath := filepath.Join(zutHome, updateCheckFile)
74 if c, ok := readUpdateCache(cachePath); ok {74 if c, ok := readUpdateCache(cachePath); ok {
75 // Cache is fresh and tracks the same binary version.75 // Cache is fresh and tracks the same binary version.
76 // Additional guard: only trust the cache when it already76 // Additional guard: only trust the cache when it already
@@ -111,13 +111,13 @@ func CheckForUpdate(ctx context.Context, zotHome, currentVersion string) UpdateI
111 // CheckForUpdateAsync runs CheckForUpdate in a goroutine, delivers the111 // CheckForUpdateAsync runs CheckForUpdate in a goroutine, delivers the
112 // result to the returned channel, and never blocks startup. The112 // result to the returned channel, and never blocks startup. The
113 // channel is always closed; receivers should `ok`-check.113 // channel is always closed; receivers should `ok`-check.
114-func CheckForUpdateAsync(zotHome, currentVersion string) <-chan UpdateInfo {114+func CheckForUpdateAsync(zutHome, currentVersion string) <-chan UpdateInfo {
115 ch := make(chan UpdateInfo, 1)115 ch := make(chan UpdateInfo, 1)
116 go func() {116 go func() {
117 defer close(ch)117 defer close(ch)
118 ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)118 ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
119 defer cancel()119 defer cancel()
120- ch <- CheckForUpdate(ctx, zotHome, currentVersion)120+ ch <- CheckForUpdate(ctx, zutHome, currentVersion)
121 }()121 }()
122 return ch122 return ch
123 }123 }
@@ -133,7 +133,7 @@ func buildInfo(current, latest, url string) UpdateInfo {
133 }133 }
134 134
135 // versionLess returns a < b for dotted semver-ish tags like "0.0.4".135 // 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's136+// Non-numeric components compare as zero, which is fine for zut's
137 // x.y.z-only scheme.137 // x.y.z-only scheme.
138 func versionLess(a, b string) bool {138 func versionLess(a, b string) bool {
139 as := splitVersion(a)139 as := splitVersion(a)
M packages/agent/updatecmd.go +36 -36
@@ -16,7 +16,7 @@ import (
16 "time"16 "time"
17 )17 )
18 18
19-// runUpdateCommand dispatches `zot update`. Returns (handled=true, err)19+// runUpdateCommand dispatches `zut update`. Returns (handled=true, err)
20 // if rawArgs starts with "update"; otherwise (handled=false, nil) so20 // if rawArgs starts with "update"; otherwise (handled=false, nil) so
21 // the main router falls through to the regular flag parser. Mirrors21 // the main router falls through to the regular flag parser. Mirrors
22 // the shape of runBotCommand / runExtCommand on purpose: identical22 // the shape of runBotCommand / runExtCommand on purpose: identical
@@ -31,7 +31,7 @@ import (
31 // name template defined in .goreleaser.yaml.31 // name template defined in .goreleaser.yaml.
32 // 3. Downloads checksums.txt and the asset to a temp directory.32 // 3. Downloads checksums.txt and the asset to a temp directory.
33 // 4. Verifies the asset's sha256 against checksums.txt.33 // 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.
35 // 6. Atomically replaces the running binary with the new one.35 // 6. Atomically replaces the running binary with the new one.
36 //36 //
37 // Refuses to operate on development builds because there is no meaningful37 // 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
58 }58 }
59 59
60 func printUpdateHelp() {60 func printUpdateHelp() {
61- fmt.Fprintln(os.Stderr, `zot update — replace the current zot binary with the latest release61+ fmt.Fprintln(os.Stderr, `zut update — replace the current zut binary with the latest release
62 62
63 usage:63 usage:
64- zot update download and install the newest release64+ zut update download and install the newest release
65- zot update --check show what update is available; install nothing65+ zut update --check show what update is available; install nothing
66- zot update --help show this help66+ zut update --help show this help
67 67
68 notes:68 notes:
69 * The binary must be writable by the current user. On a system-wide69 * 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.
71 * Development builds are refused. They typically come from a local71 * Development builds are refused. They typically come from a local
72 source build and shouldn't be silently replaced with a release binary.72 source build and shouldn't be silently replaced with a release binary.
73 * Honours $GITHUB_TOKEN if set, so private-repo releases work.73 * Honours $GITHUB_TOKEN if set, so private-repo releases work.
74 * After the binary is installed, every extension under74 * After the binary is installed, every extension under
75- $ZOT_HOME/extensions/ that is a git checkout is fast-forward75+ $ZUT_HOME/extensions/ that is a git checkout is fast-forward
76 pulled (no merge, no rebase). Dirty worktrees are stashed and76 pulled (no merge, no rebase). Dirty worktrees are stashed and
77 restored. Extensions without a .git directory, disabled77 restored. Extensions without a .git directory, disabled
78 extensions, and pulls that fail (offline, diverged, etc.) are78 extensions, and pulls that fail (offline, diverged, etc.) are
79- skipped per-extension and never abort the overall update. zot79+ skipped per-extension and never abort the overall update. zut
80 does NOT run any build step after pulling — authors are expected80 does NOT run any build step after pulling — authors are expected
81 to commit a working binary, or you can rebuild manually and81 to commit a working binary, or you can rebuild manually and
82 /reload-ext.`)82 /reload-ext.`)
@@ -87,7 +87,7 @@ notes:
87 // pipe into scripts.87 // pipe into scripts.
88 func runUpdateCheck(version string) error {88 func runUpdateCheck(version string) error {
89 if isDevVersion(version) {89 if isDevVersion(version) {
90- fmt.Println("zot:", devVersionNotice)90+ fmt.Println("zut:", devVersionNotice)
91 return nil91 return nil
92 }92 }
93 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)93 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
@@ -99,24 +99,24 @@ func runUpdateCheck(version string) error {
99 latest := strings.TrimPrefix(tag, "v")99 latest := strings.TrimPrefix(tag, "v")
100 current := versionOnly(version)100 current := versionOnly(version)
101 if !versionLess(current, latest) {101 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)
103 return nil103 return nil
104 }104 }
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)
106 return nil106 return nil
107 }107 }
108 108
109-// runUpdate is the meat of `zot update`.109+// runUpdate is the meat of `zut update`.
110 func runUpdate(version string) error {110 func runUpdate(version string) error {
111 if isDevVersion(version) {111 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")
113 }113 }
114 current := versionOnly(version)114 current := versionOnly(version)
115 115
116 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)116 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
117 defer cancel()117 defer cancel()
118 118
119- fmt.Println("zot update: querying latest release...")119+ fmt.Println("zut update: querying latest release...")
120 tag, releaseURL, err := fetchLatestRelease(ctx)120 tag, releaseURL, err := fetchLatestRelease(ctx)
121 if err != nil {121 if err != nil {
122 return fmt.Errorf("query latest release: %w", err)122 return fmt.Errorf("query latest release: %w", err)
@@ -124,18 +124,18 @@ func runUpdate(version string) error {
124 latest := strings.TrimPrefix(tag, "v")124 latest := strings.TrimPrefix(tag, "v")
125 125
126 if !versionLess(current, latest) {126 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)
128 return nil128 return nil
129 }129 }
130- fmt.Printf("zot update: %s -> %s\n", current, latest)130+ fmt.Printf("zut update: %s -> %s\n", current, latest)
131- fmt.Printf("zot update: release page %s\n", releaseURL)131+ fmt.Printf("zut update: release page %s\n", releaseURL)
132 132
133 // Pick the archive matching this platform.133 // Pick the archive matching this platform.
134 assetName, archiveFmt, err := releaseAssetName(latest)134 assetName, archiveFmt, err := releaseAssetName(latest)
135 if err != nil {135 if err != nil {
136 return err136 return err
137 }137 }
138- fmt.Printf("zot update: target asset %s\n", assetName)138+ fmt.Printf("zut update: target asset %s\n", assetName)
139 139
140 // We assume the standard GoReleaser layout: assets live under140 // We assume the standard GoReleaser layout: assets live under
141 // https://github.com/<owner>/<repo>/releases/download/<tag>/<file>141 // https://github.com/<owner>/<repo>/releases/download/<tag>/<file>
@@ -146,7 +146,7 @@ func runUpdate(version string) error {
146 assetURL := base + "/" + assetName146 assetURL := base + "/" + assetName
147 sumsURL := base + "/checksums.txt"147 sumsURL := base + "/checksums.txt"
148 148
149- tmp, err := os.MkdirTemp("", "zot-update-")149+ tmp, err := os.MkdirTemp("", "zut-update-")
150 if err != nil {150 if err != nil {
151 return fmt.Errorf("create temp dir: %w", err)151 return fmt.Errorf("create temp dir: %w", err)
152 }152 }
@@ -155,7 +155,7 @@ func runUpdate(version string) error {
155 // users can clear /tmp themselves.155 // users can clear /tmp themselves.
156 defer func() { _ = os.RemoveAll(tmp) }()156 defer func() { _ = os.RemoveAll(tmp) }()
157 157
158- fmt.Println("zot update: downloading checksums.txt...")158+ fmt.Println("zut update: downloading checksums.txt...")
159 sumsPath := filepath.Join(tmp, "checksums.txt")159 sumsPath := filepath.Join(tmp, "checksums.txt")
160 if err := downloadFile(ctx, sumsURL, sumsPath); err != nil {160 if err := downloadFile(ctx, sumsURL, sumsPath); err != nil {
161 return fmt.Errorf("download checksums: %w", err)161 return fmt.Errorf("download checksums: %w", err)
@@ -165,13 +165,13 @@ func runUpdate(version string) error {
165 return err165 return err
166 }166 }
167 167
168- fmt.Println("zot update: downloading archive...")168+ fmt.Println("zut update: downloading archive...")
169 archivePath := filepath.Join(tmp, assetName)169 archivePath := filepath.Join(tmp, assetName)
170 if err := downloadFile(ctx, assetURL, archivePath); err != nil {170 if err := downloadFile(ctx, assetURL, archivePath); err != nil {
171 return fmt.Errorf("download archive: %w", err)171 return fmt.Errorf("download archive: %w", err)
172 }172 }
173 173
174- fmt.Println("zot update: verifying checksum...")174+ fmt.Println("zut update: verifying checksum...")
175 gotSum, err := sha256File(archivePath)175 gotSum, err := sha256File(archivePath)
176 if err != nil {176 if err != nil {
177 return fmt.Errorf("hash archive: %w", err)177 return fmt.Errorf("hash archive: %w", err)
@@ -180,7 +180,7 @@ func runUpdate(version string) error {
180 return fmt.Errorf("checksum mismatch for %s: got %s, want %s", assetName, gotSum, wantSum)180 return fmt.Errorf("checksum mismatch for %s: got %s, want %s", assetName, gotSum, wantSum)
181 }181 }
182 182
183- fmt.Println("zot update: extracting...")183+ fmt.Println("zut update: extracting...")
184 extractDir := filepath.Join(tmp, "extracted")184 extractDir := filepath.Join(tmp, "extracted")
185 if err := os.MkdirAll(extractDir, 0o755); err != nil {185 if err := os.MkdirAll(extractDir, 0o755); err != nil {
186 return fmt.Errorf("mkdir extract: %w", err)186 return fmt.Errorf("mkdir extract: %w", err)
@@ -189,35 +189,35 @@ func runUpdate(version string) error {
189 return fmt.Errorf("extract archive: %w", err)189 return fmt.Errorf("extract archive: %w", err)
190 }190 }
191 191
192- newBin := filepath.Join(extractDir, "zot")192+ newBin := filepath.Join(extractDir, "zut")
193 if runtime.GOOS == "windows" {193 if runtime.GOOS == "windows" {
194- newBin = filepath.Join(extractDir, "zot.exe")194+ newBin = filepath.Join(extractDir, "zut.exe")
195 }195 }
196 if st, err := os.Stat(newBin); err != nil || st.IsDir() {196 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)
198 }198 }
199 199
200 curBin, err := os.Executable()200 curBin, err := os.Executable()
201 if err != nil {201 if err != nil {
202 return fmt.Errorf("resolve current binary path: %w", err)202 return fmt.Errorf("resolve current binary path: %w", err)
203 }203 }
204- // Resolve symlinks so 'zot' on $PATH that points at /usr/local/bin204+ // Resolve symlinks so 'zut' on $PATH that points at /usr/local/bin
205 // gets actually replaced rather than us writing into the symlink205 // gets actually replaced rather than us writing into the symlink
206 // target's directory while leaving the link stale.206 // target's directory while leaving the link stale.
207 if resolved, err := filepath.EvalSymlinks(curBin); err == nil {207 if resolved, err := filepath.EvalSymlinks(curBin); err == nil {
208 curBin = resolved208 curBin = resolved
209 }209 }
210 210
211- fmt.Printf("zot update: replacing %s\n", curBin)211+ fmt.Printf("zut update: replacing %s\n", curBin)
212 if err := replaceBinary(curBin, newBin); err != nil {212 if err := replaceBinary(curBin, newBin); err != nil {
213 return fmt.Errorf("replace binary: %w", err)213 return fmt.Errorf("replace binary: %w", err)
214 }214 }
215- fmt.Printf("zot update: installed %s\n", latest)215+ fmt.Printf("zut update: installed %s\n", latest)
216 216
217 // Best-effort: also refresh installed extensions that live in217 // Best-effort: also refresh installed extensions that live in
218 // git checkouts. Failures here are advisory and never abort the218 // git checkouts. Failures here are advisory and never abort the
219 // overall update — the binary swap already succeeded.219 // overall update — the binary swap already succeeded.
220- updateAllExtensions(ZotHome())220+ updateAllExtensions(ZutHome())
221 221
222 return nil222 return nil
223 }223 }
@@ -236,13 +236,13 @@ func releaseAssetName(version string) (name, format string, err error) {
236 case "windows":236 case "windows":
237 // supported237 // supported
238 default:238 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)
240 }240 }
241 switch goarch {241 switch goarch {
242 case "amd64", "arm64":242 case "amd64", "arm64":
243 // supported243 // supported
244 default:244 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)
246 }246 }
247 if goos == "windows" && goarch == "arm64" {247 if goos == "windows" && goarch == "arm64" {
248 return "", "", errors.New("windows/arm64 release artifacts are not published; download manually")248 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) {
251 if goos == "windows" {251 if goos == "windows" {
252 ext = "zip"252 ext = "zip"
253 }253 }
254- return fmt.Sprintf("zot_%s_%s_%s.%s", version, goos, goarch, ext), ext, nil254+ return fmt.Sprintf("zut_%s_%s_%s.%s", version, goos, goarch, ext), ext, nil
255 }255 }
256 256
257 // downloadFile fetches url to dst, streaming through io.Copy so big257 // downloadFile fetches url to dst, streaming through io.Copy so big
@@ -325,7 +325,7 @@ func sha256File(path string) (string, error) {
325 }325 }
326 326
327 // extractArchive shells out to the system tar / unzip rather than327 // extractArchive shells out to the system tar / unzip rather than
328-// pulling in a Go archive lib. Reasoning: zot already depends on328+// pulling in a Go archive lib. Reasoning: zut already depends on
329 // system tar implicitly in a few places, every supported platform329 // system tar implicitly in a few places, every supported platform
330 // ships tar (BSD tar on macOS handles gzip natively, GNU tar on330 // ships tar (BSD tar on macOS handles gzip natively, GNU tar on
331 // Linux, bsdtar on Windows 10+), and the dependency-free release331 // Linux, bsdtar on Windows 10+), and the dependency-free release
@@ -385,7 +385,7 @@ func replaceBinary(cur, newBin string) error {
385 return fmt.Errorf("install new binary: %w", err)385 return fmt.Errorf("install new binary: %w", err)
386 }386 }
387 // The .old file is locked until this process exits; leave387 // The .old file is locked until this process exits; leave
388- // it behind. Next `zot update` cleans it up via the388+ // it behind. Next `zut update` cleans it up via the
389 // os.Remove(bak) above.389 // os.Remove(bak) above.
390 return nil390 return nil
391 }391 }
R packages/agent/zotfile.go → packages/agent/zutfile.go +120 -120 renamed, 79% similar
@@ -20,22 +20,22 @@ import (
20 "strings"20 "strings"
21 "time"21 "time"
22 22
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"
23 "github.com/klauspost/compress/zstd"27 "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"
28 "golang.org/x/term"28 "golang.org/x/term"
29 )29 )
30 30
31-type ZotfileManifest struct {31+type ZutfileManifest struct {
32- Zotfile int `json:"zotfile"`32+ Zutfile int `json:"zutfile"`
33 Name string `json:"name"`33 Name string `json:"name"`
34 Version string `json:"version"`34 Version string `json:"version"`
35 Description string `json:"description"`35 Description string `json:"description"`
36 License string `json:"license"`36 License string `json:"license"`
37 Runtime struct {37 Runtime struct {
38- MinZot string `json:"min_zot"`38+ MinZut string `json:"min_zut"`
39 } `json:"runtime"`39 } `json:"runtime"`
40 Model struct {40 Model struct {
41 Requires []string `json:"requires"`41 Requires []string `json:"requires"`
@@ -56,14 +56,14 @@ type ZotfileManifest struct {
56 ReplaceSystemPrompt bool `json:"replace_system_prompt"`56 ReplaceSystemPrompt bool `json:"replace_system_prompt"`
57 }57 }
58 58
59-type zotfileLoaded struct {59+type zutfileLoaded struct {
60 Dir string60 Dir string
61 Temp bool61 Temp bool
62 Digest string62 Digest string
63- Manifest ZotfileManifest63+ Manifest ZutfileManifest
64 }64 }
65 65
66-func runZotfileCommand(rawArgs []string, version string) (bool, error) {66+func runZutfileCommand(rawArgs []string, version string) (bool, error) {
67 if len(rawArgs) == 0 {67 if len(rawArgs) == 0 {
68 return false, nil68 return false, nil
69 }69 }
@@ -77,17 +77,17 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) {
77 if len(rawArgs) > 2 {77 if len(rawArgs) > 2 {
78 out = rawArgs[2]78 out = rawArgs[2]
79 }79 }
80- return true, zotPack(dir, out)80+ return true, zutPack(dir, out)
81 case "inspect":81 case "inspect":
82 if len(rawArgs) < 2 {82 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")
84 }84 }
85- return true, zotInspect(rawArgs[1])85+ return true, zutInspect(rawArgs[1])
86 case "verify":86 case "verify":
87 if len(rawArgs) < 2 {87 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")
89 }89 }
90- zf, cleanup, err := loadZotfile(rawArgs[1])90+ zf, cleanup, err := loadZutfile(rawArgs[1])
91 if cleanup != nil {91 if cleanup != nil {
92 defer cleanup()92 defer cleanup()
93 }93 }
@@ -98,11 +98,11 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) {
98 return true, nil98 return true, nil
99 case "run":99 case "run":
100 if len(rawArgs) < 2 {100 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")
102 }102 }
103 yes, remaining := peelLeadingYes(rawArgs[1:])103 yes, remaining := peelLeadingYes(rawArgs[1:])
104 if len(remaining) < 1 {104 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")
106 }106 }
107 ref := remaining[0]107 ref := remaining[0]
108 rest := remaining[1:]108 rest := remaining[1:]
@@ -114,37 +114,37 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) {
114 if yes {114 if yes {
115 args.Yes = true115 args.Yes = true
116 }116 }
117- return true, runLocalZotfile(ref, args, version)117+ return true, runLocalZutfile(ref, args, version)
118 default:118 default:
119 return false, nil119 return false, nil
120 }120 }
121 }121 }
122 122
123-func runLocalZotfile(ref string, args Args, version string) error {123+func runLocalZutfile(ref string, args Args, version string) error {
124 prepareRuntimeCatalog()124 prepareRuntimeCatalog()
125- zf, cleanup, err := loadZotfile(ref)125+ zf, cleanup, err := loadZutfile(ref)
126 if cleanup != nil {126 if cleanup != nil {
127 defer cleanup()127 defer cleanup()
128 }128 }
129 if err != nil {129 if err != nil {
130 return err130 return err
131 }131 }
132- if err := checkZotfileRequirements(zf, version); err != nil {132+ if err := checkZutfileRequirements(zf, version); err != nil {
133 return err133 return err
134 }134 }
135- agentData := filepath.Join(ZotHome(), "agents", safeAgentName(zf.Manifest.Name), "data")135+ agentData := filepath.Join(ZutHome(), "agents", safeAgentName(zf.Manifest.Name), "data")
136 if err := os.MkdirAll(agentData, 0o755); err != nil {136 if err := os.MkdirAll(agentData, 0o755); err != nil {
137 return err137 return err
138 }138 }
139 perms := zf.Manifest.Permissions.Expand(args.CWD, agentData)139 perms := zf.Manifest.Permissions.Expand(args.CWD, agentData)
140- allowed, err := consentZotfile(zf, perms, args.Yes)140+ allowed, err := consentZutfile(zf, perms, args.Yes)
141 if err != nil {141 if err != nil {
142 return err142 return err
143 }143 }
144 if !allowed {144 if !allowed {
145 return nil145 return nil
146 }146 }
147- if err := applyZotfileModelRequirements(&args, zf.Manifest); err != nil {147+ if err := applyZutfileModelRequirements(&args, zf.Manifest); err != nil {
148 return err148 return err
149 }149 }
150 agentPath := filepath.Join(zf.Dir, "AGENT.md")150 agentPath := filepath.Join(zf.Dir, "AGENT.md")
@@ -167,13 +167,13 @@ func runLocalZotfile(ref string, args Args, version string) error {
167 args.Prompt = *zf.Manifest.Entry.DefaultPrompt167 args.Prompt = *zf.Manifest.Entry.DefaultPrompt
168 }168 }
169 if dirExists(filepath.Join(zf.Dir, "skills")) {169 if dirExists(filepath.Join(zf.Dir, "skills")) {
170- old := os.Getenv("ZOT_AGENT_SKILLS")170+ old := os.Getenv("ZUT_AGENT_SKILLS")
171 v := filepath.Join(zf.Dir, "skills")171 v := filepath.Join(zf.Dir, "skills")
172 if old != "" {172 if old != "" {
173 v += string(os.PathListSeparator) + old173 v += string(os.PathListSeparator) + old
174 }174 }
175- _ = os.Setenv("ZOT_AGENT_SKILLS", v)175+ _ = os.Setenv("ZUT_AGENT_SKILLS", v)
176- defer os.Setenv("ZOT_AGENT_SKILLS", old)176+ defer os.Setenv("ZUT_AGENT_SKILLS", old)
177 }177 }
178 args.AgentName = zf.Manifest.Name178 args.AgentName = zf.Manifest.Name
179 args.AgentDataDir = agentData179 args.AgentDataDir = agentData
@@ -181,8 +181,8 @@ func runLocalZotfile(ref string, args Args, version string) error {
181 return runWithArgs(args, version)181 return runWithArgs(args, version)
182 }182 }
183 183
184-func zotInspect(ref string) error {184+func zutInspect(ref string) error {
185- zf, cleanup, err := loadZotfile(ref)185+ zf, cleanup, err := loadZutfile(ref)
186 if cleanup != nil {186 if cleanup != nil {
187 defer cleanup()187 defer cleanup()
188 }188 }
@@ -207,7 +207,7 @@ func zotInspect(ref string) error {
207 })207 })
208 }208 }
209 209
210-func applyZotfileModelRequirements(args *Args, m ZotfileManifest) error {210+func applyZutfileModelRequirements(args *Args, m ZutfileManifest) error {
211 minCtx := m.Model.MinContext211 minCtx := m.Model.MinContext
212 requires := map[string]bool{}212 requires := map[string]bool{}
213 for _, capability := range m.Model.Requires {213 for _, capability := range m.Model.Requires {
@@ -219,7 +219,7 @@ func applyZotfileModelRequirements(args *Args, m ZotfileManifest) error {
219 }219 }
220 }220 }
221 if m.Model.MinTier != "" {221 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")
223 }223 }
224 compatible := func(model provider.Model) bool {224 compatible := func(model provider.Model) bool {
225 if model.ContextWindow < minCtx {225 if model.ContextWindow < minCtx {
@@ -289,27 +289,27 @@ func prepareRuntimeCatalog() {
289 RefreshModelsAsync()289 RefreshModelsAsync()
290 }290 }
291 291
292-func zotPack(dir, out string) error {292+func zutPack(dir, out string) error {
293 abs, err := filepath.Abs(dir)293 abs, err := filepath.Abs(dir)
294 if err != nil {294 if err != nil {
295 return err295 return err
296 }296 }
297- if _, err := readZotManifest(abs); err != nil {297+ if _, err := readZutManifest(abs); err != nil {
298 return err298 return err
299 }299 }
300- if err := validateZotfileDir(abs); err != nil {300+ if err := validateZutfileDir(abs); err != nil {
301 return err301 return err
302 }302 }
303 if out == "" {303 if out == "" {
304- m, _ := readZotManifest(abs)304+ m, _ := readZutManifest(abs)
305 base := safeAgentName(m.Name)305 base := safeAgentName(m.Name)
306 if base == "" {306 if base == "" {
307 base = filepath.Base(abs)307 base = filepath.Base(abs)
308 }308 }
309- out = base + ".zot"309+ out = base + ".zut"
310 }310 }
311- if filepath.Ext(out) != ".zot" {311+ if filepath.Ext(out) != ".zut" {
312- out += ".zot"312+ out += ".zut"
313 }313 }
314 outAbs, err := filepath.Abs(out)314 outAbs, err := filepath.Abs(out)
315 if err != nil {315 if err != nil {
@@ -337,53 +337,53 @@ func zotPack(dir, out string) error {
337 return nil337 return nil
338 }338 }
339 339
340-func loadZotfile(ref string) (zotfileLoaded, func(), error) {340+func loadZutfile(ref string) (zutfileLoaded, func(), error) {
341- resolved, err := resolveZotfileRef(ref)341+ resolved, err := resolveZutfileRef(ref)
342 if err != nil {342 if err != nil {
343- return zotfileLoaded{}, nil, err343+ return zutfileLoaded{}, nil, err
344 }344 }
345 ref = resolved345 ref = resolved
346 if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") {346 if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
347- return loadRemoteZotfile(u)347+ return loadRemoteZutfile(u)
348 }348 }
349 info, err := os.Stat(ref)349 info, err := os.Stat(ref)
350 if err != nil {350 if err != nil {
351- return zotfileLoaded{}, nil, err351+ return zutfileLoaded{}, nil, err
352 }352 }
353 if info.IsDir() {353 if info.IsDir() {
354 abs, _ := filepath.Abs(ref)354 abs, _ := filepath.Abs(ref)
355- m, err := readZotManifest(abs)355+ m, err := readZutManifest(abs)
356 if err != nil {356 if err != nil {
357- return zotfileLoaded{}, nil, err357+ return zutfileLoaded{}, nil, err
358 }358 }
359- if err := validateZotfileDir(abs); err != nil {359+ if err := validateZutfileDir(abs); err != nil {
360- return zotfileLoaded{}, nil, err360+ return zutfileLoaded{}, nil, err
361 }361 }
362- return zotfileLoaded{Dir: abs, Manifest: m, Digest: digestDirectory(abs)}, nil, nil362+ return zutfileLoaded{Dir: abs, Manifest: m, Digest: digestDirectory(abs)}, nil, nil
363 }363 }
364- tmp, err := os.MkdirTemp("", "zotfile-*")364+ tmp, err := os.MkdirTemp("", "zutfile-*")
365 if err != nil {365 if err != nil {
366- return zotfileLoaded{}, nil, err366+ return zutfileLoaded{}, nil, err
367 }367 }
368 cleanup := func() { _ = os.RemoveAll(tmp) }368 cleanup := func() { _ = os.RemoveAll(tmp) }
369- digest, err := unpackZotArchive(ref, tmp)369+ digest, err := unpackZutArchive(ref, tmp)
370 if err != nil {370 if err != nil {
371 cleanup()371 cleanup()
372- return zotfileLoaded{}, nil, err372+ return zutfileLoaded{}, nil, err
373 }373 }
374- m, err := readZotManifest(tmp)374+ m, err := readZutManifest(tmp)
375 if err != nil {375 if err != nil {
376 cleanup()376 cleanup()
377- return zotfileLoaded{}, nil, err377+ return zutfileLoaded{}, nil, err
378 }378 }
379- if err := validateZotfileDir(tmp); err != nil {379+ if err := validateZutfileDir(tmp); err != nil {
380 cleanup()380 cleanup()
381- return zotfileLoaded{}, nil, err381+ return zutfileLoaded{}, nil, err
382 }382 }
383- return zotfileLoaded{Dir: tmp, Temp: true, Digest: digest, Manifest: m}, cleanup, nil383+ return zutfileLoaded{Dir: tmp, Temp: true, Digest: digest, Manifest: m}, cleanup, nil
384 }384 }
385 385
386-func resolveZotfileRef(ref string) (string, error) {386+func resolveZutfileRef(ref string) (string, error) {
387 if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") {387 if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
388 return ref, nil388 return ref, nil
389 }389 }
@@ -393,24 +393,24 @@ func resolveZotfileRef(ref string) (string, error) {
393 return "", err393 return "", err
394 }394 }
395 if filepath.Ext(ref) == "" {395 if filepath.Ext(ref) == "" {
396- archive := ref + ".zot"396+ archive := ref + ".zut"
397 if _, err := os.Stat(archive); err == nil {397 if _, err := os.Stat(archive); err == nil {
398 return archive, nil398 return archive, nil
399 } else if !os.IsNotExist(err) {399 } else if !os.IsNotExist(err) {
400 return "", err400 return "", err
401 }401 }
402 }402 }
403- if strings.HasSuffix(strings.ToLower(ref), ".zot") {403+ if strings.HasSuffix(strings.ToLower(ref), ".zut") {
404 return ref, nil404 return ref, nil
405 }405 }
406 githubRef := strings.ReplaceAll(ref, "\\", "/")406 githubRef := strings.ReplaceAll(ref, "\\", "/")
407 parts := strings.Split(githubRef, "/")407 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]) {
409 return "https://github.com/" + parts[0] + "/" + parts[1], nil409 return "https://github.com/" + parts[0] + "/" + parts[1], nil
410 }410 }
411 if len(parts) >= 3 {411 if len(parts) >= 3 {
412 for _, part := range parts {412 for _, part := range parts {
413- if !validGitHubZotfileSegment(part) {413+ if !validGitHubZutfileSegment(part) {
414 return ref, nil414 return ref, nil
415 }415 }
416 }416 }
@@ -419,37 +419,37 @@ func resolveZotfileRef(ref string) (string, error) {
419 return ref, nil419 return ref, nil
420 }420 }
421 421
422-func validGitHubZotfileSegment(s string) bool {422+func validGitHubZutfileSegment(s string) bool {
423 return s != "" && s == strings.ToLower(s) && safeAgentName(s) == s423 return s != "" && s == strings.ToLower(s) && safeAgentName(s) == s
424 }424 }
425 425
426-var zotfileHTTPClient = &http.Client{Timeout: 60 * time.Second}426+var zutfileHTTPClient = &http.Client{Timeout: 60 * time.Second}
427 427
428 var githubArchiveURL = func(owner, repo, ref string) string {428 var githubArchiveURL = func(owner, repo, ref string) string {
429 return fmt.Sprintf("https://github.com/%s/%s/archive/%s.tar.gz", owner, repo, url.PathEscape(ref))429 return fmt.Sprintf("https://github.com/%s/%s/archive/%s.tar.gz", owner, repo, url.PathEscape(ref))
430 }430 }
431 431
432-func loadRemoteZotfile(u *url.URL) (zotfileLoaded, func(), error) {432+func loadRemoteZutfile(u *url.URL) (zutfileLoaded, func(), error) {
433 if !strings.EqualFold(u.Hostname(), "github.com") {433 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())
435 }435 }
436 owner, repo, ref, subdir, err := parseGitHubAgentURL(u)436 owner, repo, ref, subdir, err := parseGitHubAgentURL(u)
437 if err != nil {437 if err != nil {
438- return zotfileLoaded{}, nil, err438+ return zutfileLoaded{}, nil, err
439 }439 }
440- tmp, err := os.MkdirTemp("", "zotfile-github-*")440+ tmp, err := os.MkdirTemp("", "zutfile-github-*")
441 if err != nil {441 if err != nil {
442- return zotfileLoaded{}, nil, err442+ return zutfileLoaded{}, nil, err
443 }443 }
444 cleanup := func() { _ = os.RemoveAll(tmp) }444 cleanup := func() { _ = os.RemoveAll(tmp) }
445 if err := downloadGitHubArchive(githubArchiveURL(owner, repo, ref), tmp); err != nil {445 if err := downloadGitHubArchive(githubArchiveURL(owner, repo, ref), tmp); err != nil {
446 cleanup()446 cleanup()
447- return zotfileLoaded{}, nil, err447+ return zutfileLoaded{}, nil, err
448 }448 }
449 root, err := singleExtractedRoot(tmp)449 root, err := singleExtractedRoot(tmp)
450 if err != nil {450 if err != nil {
451 cleanup()451 cleanup()
452- return zotfileLoaded{}, nil, err452+ return zutfileLoaded{}, nil, err
453 }453 }
454 dir := filepath.Join(root, filepath.FromSlash(subdir))454 dir := filepath.Join(root, filepath.FromSlash(subdir))
455 if subdir == "" {455 if subdir == "" {
@@ -457,18 +457,18 @@ func loadRemoteZotfile(u *url.URL) (zotfileLoaded, func(), error) {
457 }457 }
458 if !pathWithin(root, dir) {458 if !pathWithin(root, dir) {
459 cleanup()459 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)
461 }461 }
462- m, err := readZotManifest(dir)462+ m, err := readZutManifest(dir)
463 if err != nil {463 if err != nil {
464 cleanup()464 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)
466 }466 }
467- if err := validateZotfileDir(dir); err != nil {467+ if err := validateZutfileDir(dir); err != nil {
468 cleanup()468 cleanup()
469- return zotfileLoaded{}, nil, err469+ return zutfileLoaded{}, nil, err
470 }470 }
471- return zotfileLoaded{Dir: dir, Temp: true, Digest: digestDirectory(dir), Manifest: m}, cleanup, nil471+ return zutfileLoaded{Dir: dir, Temp: true, Digest: digestDirectory(dir), Manifest: m}, cleanup, nil
472 }472 }
473 473
474 func parseGitHubAgentURL(u *url.URL) (owner, repo, ref, subdir string, err error) {474 func parseGitHubAgentURL(u *url.URL) (owner, repo, ref, subdir string, err error) {
@@ -507,8 +507,8 @@ func downloadGitHubArchive(archiveURL, dst string) error {
507 if err != nil {507 if err != nil {
508 return err508 return err
509 }509 }
510- req.Header.Set("User-Agent", "zot")510+ req.Header.Set("User-Agent", "zut")
511- resp, err := zotfileHTTPClient.Do(req)511+ resp, err := zutfileHTTPClient.Do(req)
512 if err != nil {512 if err != nil {
513 return fmt.Errorf("download GitHub repository: %w", err)513 return fmt.Errorf("download GitHub repository: %w", err)
514 }514 }
@@ -516,7 +516,7 @@ func downloadGitHubArchive(archiveURL, dst string) error {
516 if resp.StatusCode != http.StatusOK {516 if resp.StatusCode != http.StatusOK {
517 return fmt.Errorf("download GitHub repository: HTTP %d", resp.StatusCode)517 return fmt.Errorf("download GitHub repository: HTTP %d", resp.StatusCode)
518 }518 }
519- limited := &io.LimitedReader{R: resp.Body, N: maxZotfileCompressedSize + 1}519+ limited := &io.LimitedReader{R: resp.Body, N: maxZutfileCompressedSize + 1}
520 gr, err := gzip.NewReader(limited)520 gr, err := gzip.NewReader(limited)
521 if err != nil {521 if err != nil {
522 return fmt.Errorf("read GitHub archive: %w", err)522 return fmt.Errorf("read GitHub archive: %w", err)
@@ -526,7 +526,7 @@ func downloadGitHubArchive(archiveURL, dst string) error {
526 return fmt.Errorf("extract GitHub archive: %w", err)526 return fmt.Errorf("extract GitHub archive: %w", err)
527 }527 }
528 if limited.N <= 0 {528 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)
530 }530 }
531 return nil531 return nil
532 }532 }
@@ -547,8 +547,8 @@ func singleExtractedRoot(dir string) (string, error) {
547 return filepath.Join(dir, entries[0].Name()), nil547 return filepath.Join(dir, entries[0].Name()), nil
548 }548 }
549 549
550-func readZotManifest(dir string) (ZotfileManifest, error) {550+func readZutManifest(dir string) (ZutfileManifest, error) {
551- var m ZotfileManifest551+ var m ZutfileManifest
552 b, err := os.ReadFile(filepath.Join(dir, "manifest.json"))552 b, err := os.ReadFile(filepath.Join(dir, "manifest.json"))
553 if err != nil {553 if err != nil {
554 return m, fmt.Errorf("manifest.json is required: %w", err)554 return m, fmt.Errorf("manifest.json is required: %w", err)
@@ -556,8 +556,8 @@ func readZotManifest(dir string) (ZotfileManifest, error) {
556 if err := json.Unmarshal(b, &m); err != nil {556 if err := json.Unmarshal(b, &m); err != nil {
557 return m, fmt.Errorf("manifest.json: %w", err)557 return m, fmt.Errorf("manifest.json: %w", err)
558 }558 }
559- if m.Zotfile != 1 {559+ if m.Zutfile != 1 {
560- return m, fmt.Errorf("unsupported zotfile version %d", m.Zotfile)560+ return m, fmt.Errorf("unsupported zutfile version %d", m.Zutfile)
561 }561 }
562 name := strings.TrimSpace(m.Name)562 name := strings.TrimSpace(m.Name)
563 if name == "" {563 if name == "" {
@@ -582,7 +582,7 @@ func readZotManifest(dir string) (ZotfileManifest, error) {
582 return m, nil582 return m, nil
583 }583 }
584 584
585-func validateZotfileDir(dir string) error {585+func validateZutfileDir(dir string) error {
586 agentPath := filepath.Join(dir, "AGENT.md")586 agentPath := filepath.Join(dir, "AGENT.md")
587 st, err := os.Stat(agentPath)587 st, err := os.Stat(agentPath)
588 switch {588 switch {
@@ -602,25 +602,25 @@ func validateZotfileDir(dir string) error {
602 }602 }
603 603
604 const (604 const (
605- maxZotfileCompressedSize = 100 << 20605+ maxZutfileCompressedSize = 100 << 20
606- maxZotfileEntrySize = 64 << 20606+ maxZutfileEntrySize = 64 << 20
607- maxZotfileExpandedSize = 256 << 20607+ maxZutfileExpandedSize = 256 << 20
608 )608 )
609 609
610-func unpackZotArchive(path, dst string) (string, error) {610+func unpackZutArchive(path, dst string) (string, error) {
611 info, err := os.Stat(path)611 info, err := os.Stat(path)
612 if err != nil {612 if err != nil {
613 return "", err613 return "", err
614 }614 }
615- if info.Size() > maxZotfileCompressedSize {615+ if info.Size() > maxZutfileCompressedSize {
616- return "", fmt.Errorf("zotfile exceeds %d MiB compressed size limit", maxZotfileCompressedSize>>20)616+ return "", fmt.Errorf("zutfile exceeds %d MiB compressed size limit", maxZutfileCompressedSize>>20)
617 }617 }
618 b, err := os.ReadFile(path)618 b, err := os.ReadFile(path)
619 if err != nil {619 if err != nil {
620 return "", err620 return "", err
621 }621 }
622 digest := sha256.Sum256(b)622 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))
624 if err != nil {624 if err != nil {
625 // Development fallback for older experiments.625 // Development fallback for older experiments.
626 gr, gerr := gzip.NewReader(bytes.NewReader(b))626 gr, gerr := gzip.NewReader(bytes.NewReader(b))
@@ -650,7 +650,7 @@ func untar(r io.Reader, dst string) error {
650 return fmt.Errorf("unsafe path in archive: %s", h.Name)650 return fmt.Errorf("unsafe path in archive: %s", h.Name)
651 }651 }
652 path := filepath.Join(dst, name)652 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 {
654 return fmt.Errorf("archive content exceeds extraction size limit: %s", h.Name)654 return fmt.Errorf("archive content exceeds extraction size limit: %s", h.Name)
655 }655 }
656 expanded += h.Size656 expanded += h.Size
@@ -699,7 +699,7 @@ func writeCanonicalTar(root string, w io.Writer, exclude ...string) error {
699 return nil699 return nil
700 }700 }
701 if d.Type()&os.ModeSymlink != 0 {701 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)
703 }703 }
704 files = append(files, path)704 files = append(files, path)
705 return nil705 return nil
@@ -747,13 +747,13 @@ func writeCanonicalTar(root string, w io.Writer, exclude ...string) error {
747 return nil747 return nil
748 }748 }
749 749
750-func checkZotfileRequirements(zf zotfileLoaded, version string) error {750+func checkZutfileRequirements(zf zutfileLoaded, version string) error {
751- if min := strings.TrimSpace(zf.Manifest.Runtime.MinZot); min != "" {751+ if min := strings.TrimSpace(zf.Manifest.Runtime.MinZut); min != "" {
752 if isDevVersion(version) {752 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)
754 }754 }
755 if versionLess(version, min) {755 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))
757 }757 }
758 }758 }
759 if len(zf.Manifest.Requirements.OS) > 0 {759 if len(zf.Manifest.Requirements.OS) > 0 {
@@ -794,14 +794,14 @@ func peelLeadingYes(args []string) (yes bool, rest []string) {
794 return yes, rest794 return yes, rest
795 }795 }
796 796
797-func consentZotfile(zf zotfileLoaded, perms tools.PermissionSet, autoYes bool) (bool, error) {797+func consentZutfile(zf zutfileLoaded, perms tools.PermissionSet, autoYes bool) (bool, error) {
798- if autoYes || os.Getenv("ZOT_AGENT_CONSENT") == "1" {798+ if autoYes || os.Getenv("ZUT_AGENT_CONSENT") == "1" {
799- return acceptZotfileConsent(zf, perms)799+ return acceptZutfileConsent(zf, perms)
800 }800 }
801 // "ask" deliberately requires approval on every launch. Other consent801 // "ask" deliberately requires approval on every launch. Other consent
802 // is durable only for this exact artifact digest, so any package change802 // is durable only for this exact artifact digest, so any package change
803 // causes a fresh prompt.803 // 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")
805 if strings.ToLower(strings.TrimSpace(perms.Bash.Mode)) != "ask" {805 if strings.ToLower(strings.TrimSpace(perms.Bash.Mode)) != "ask" {
806 if _, err := os.Stat(consentPath); err == nil {806 if _, err := os.Stat(consentPath); err == nil {
807 return true, nil807 return true, nil
@@ -811,27 +811,27 @@ func consentZotfile(zf zotfileLoaded, perms tools.PermissionSet, autoYes bool) (
811 th := tui.Dark811 th := tui.Dark
812 if interactive {812 if interactive {
813 cfg, _ := LoadConfig()813 cfg, _ := LoadConfig()
814- th, _, _ = tui.DetectThemeWithCustom(ZotHome(), cfg.Theme, 80*time.Millisecond)814+ th, _, _ = tui.DetectThemeWithCustom(ZutHome(), cfg.Theme, 80*time.Millisecond)
815 }815 }
816- fmt.Print(formatZotfileConsent(zf, perms, th, interactive))816+ fmt.Print(formatZutfileConsent(zf, perms, th, interactive))
817 if !interactive {817 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")
819 }819 }
820- allowed, err := readZotfileConsent(os.Stdin, os.Stdout, th)820+ allowed, err := readZutfileConsent(os.Stdin, os.Stdout, th)
821 if err != nil || !allowed {821 if err != nil || !allowed {
822 return allowed, err822 return allowed, err
823 }823 }
824- return acceptZotfileConsent(zf, perms)824+ return acceptZutfileConsent(zf, perms)
825 }825 }
826 826
827-// acceptZotfileConsent records durable consent when the bash mode is not827+// acceptZutfileConsent records durable consent when the bash mode is not
828 // "ask". Ask mode never caches receipts so every launch re-prompts unless828 // "ask". Ask mode never caches receipts so every launch re-prompts unless
829-// -y / ZOT_AGENT_CONSENT is used for that run.829+// -y / ZUT_AGENT_CONSENT is used for that run.
830-func acceptZotfileConsent(zf zotfileLoaded, perms tools.PermissionSet) (bool, error) {830+func acceptZutfileConsent(zf zutfileLoaded, perms tools.PermissionSet) (bool, error) {
831 if strings.ToLower(strings.TrimSpace(perms.Bash.Mode)) == "ask" {831 if strings.ToLower(strings.TrimSpace(perms.Bash.Mode)) == "ask" {
832 return true, nil832 return true, nil
833 }833 }
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")
835 if err := os.MkdirAll(filepath.Dir(consentPath), 0o700); err != nil {835 if err := os.MkdirAll(filepath.Dir(consentPath), 0o700); err != nil {
836 return false, fmt.Errorf("save agent consent: %w", err)836 return false, fmt.Errorf("save agent consent: %w", err)
837 }837 }
@@ -843,9 +843,9 @@ func acceptZotfileConsent(zf zotfileLoaded, perms tools.PermissionSet) (bool, er
843 return true, nil843 return true, nil
844 }844 }
845 845
846-var errZotfileConsentInterrupted = errors.New("interrupted")846+var errZutfileConsentInterrupted = errors.New("interrupted")
847 847
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 {
849 style := func(c tui.TerminalColor, text string) string {849 style := func(c tui.TerminalColor, text string) string {
850 if !color {850 if !color {
851 return text851 return text
@@ -870,10 +870,10 @@ func formatZotfileConsent(zf zotfileLoaded, perms tools.PermissionSet, th tui.Th
870 return sb.String()870 return sb.String()
871 }871 }
872 872
873-// readZotfileConsent accepts one y/n answer followed by Enter without echoing873+// readZutfileConsent accepts one y/n answer followed by Enter without echoing
874 // unrelated input. Raw mode also turns Ctrl+C into a byte so the terminal can874 // unrelated input. Raw mode also turns Ctrl+C into a byte so the terminal can
875 // be restored before the command aborts.875 // 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) {
877 fd := int(in.Fd())877 fd := int(in.Fd())
878 state, err := term.MakeRaw(fd)878 state, err := term.MakeRaw(fd)
879 if err != nil {879 if err != nil {
@@ -881,7 +881,7 @@ func readZotfileConsent(in *os.File, out io.Writer, th tui.Theme) (bool, error)
881 }881 }
882 defer func() { _ = term.Restore(fd, state) }()882 defer func() { _ = term.Restore(fd, state) }()
883 883
884- answer, err := readZotfileConsentKey(in, func(answer byte) {884+ answer, err := readZutfileConsentKey(in, func(answer byte) {
885 if answer == 0 {885 if answer == 0 {
886 fmt.Fprint(out, "\b \b")886 fmt.Fprint(out, "\b \b")
887 return887 return
@@ -889,7 +889,7 @@ func readZotfileConsent(in *os.File, out io.Writer, th tui.Theme) (bool, error)
889 fmt.Fprint(out, th.FGColor(th.FG, string(answer)))889 fmt.Fprint(out, th.FGColor(th.FG, string(answer)))
890 })890 })
891 switch {891 switch {
892- case errors.Is(err, errZotfileConsentInterrupted):892+ case errors.Is(err, errZutfileConsentInterrupted):
893 fmt.Fprint(out, "^C\r\n")893 fmt.Fprint(out, "^C\r\n")
894 return false, err894 return false, err
895 case err != nil:895 case err != nil:
@@ -901,7 +901,7 @@ func readZotfileConsent(in *os.File, out io.Writer, th tui.Theme) (bool, error)
901 }901 }
902 }902 }
903 903
904-func readZotfileConsentKey(r io.Reader, echo func(byte)) (byte, error) {904+func readZutfileConsentKey(r io.Reader, echo func(byte)) (byte, error) {
905 var answer byte905 var answer byte
906 var b [1]byte906 var b [1]byte
907 for {907 for {
@@ -928,7 +928,7 @@ func readZotfileConsentKey(r io.Reader, echo func(byte)) (byte, error) {
928 }928 }
929 }929 }
930 case 3: // Ctrl+C in raw mode.930 case 3: // Ctrl+C in raw mode.
931- return 0, errZotfileConsentInterrupted931+ return 0, errZutfileConsentInterrupted
932 }932 }
933 }933 }
934 }934 }
R packages/agent/zotfile_pre_test.go → packages/agent/zutfile_pre_test.go +8 -8 renamed, 72% similar
@@ -7,8 +7,8 @@ import (
7 "strings"7 "strings"
8 "testing"8 "testing"
9 9
10- "github.com/patriceckhart/zot/packages/core"10+ "github.com/bnema/zut/packages/core"
11- "github.com/patriceckhart/zot/packages/provider"11+ "github.com/bnema/zut/packages/provider"
12 )12 )
13 13
14 func TestStreamTextSinkDoesNotRepeatCompletedMessage(t *testing.T) {14 func TestStreamTextSinkDoesNotRepeatCompletedMessage(t *testing.T) {
@@ -26,12 +26,12 @@ func TestStreamTextSinkDoesNotRepeatCompletedMessage(t *testing.T) {
26 }26 }
27 }27 }
28 28
29-func TestRunZotfileStartupPreShellFailure(t *testing.T) {29+func TestRunZutfileStartupPreShellFailure(t *testing.T) {
30 pre := "!false"30 pre := "!false"
31 if runtime.GOOS == "windows" {31 if runtime.GOOS == "windows" {
32 pre = "!exit 1"32 pre = "!exit 1"
33 }33 }
34- err := runZotfileStartupPre(context.Background(), pre, t.TempDir(), nil, nil, nil, nil)34+ err := runZutfileStartupPre(context.Background(), pre, t.TempDir(), nil, nil, nil, nil)
35 if err == nil {35 if err == nil {
36 t.Fatal("expected entry.pre shell failure")36 t.Fatal("expected entry.pre shell failure")
37 }37 }
@@ -40,19 +40,19 @@ func TestRunZotfileStartupPreShellFailure(t *testing.T) {
40 }40 }
41 }41 }
42 42
43-func TestRunZotfileStartupPreEmpty(t *testing.T) {43+func TestRunZutfileStartupPreEmpty(t *testing.T) {
44- if err := runZotfileStartupPre(context.Background(), " ", t.TempDir(), nil, nil, nil, nil); err != nil {44+ if err := runZutfileStartupPre(context.Background(), " ", t.TempDir(), nil, nil, nil, nil); err != nil {
45 t.Fatalf("empty pre: %v", err)45 t.Fatalf("empty pre: %v", err)
46 }46 }
47 }47 }
48 48
49-func TestRunZotfileStartupPreShellStreams(t *testing.T) {49+func TestRunZutfileStartupPreShellStreams(t *testing.T) {
50 cmd := "!printf streamed-pre"50 cmd := "!printf streamed-pre"
51 if runtime.GOOS == "windows" {51 if runtime.GOOS == "windows" {
52 cmd = "!echo streamed-pre"52 cmd = "!echo streamed-pre"
53 }53 }
54 var out strings.Builder54 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 {
56 t.Fatal(err)56 t.Fatal(err)
57 }57 }
58 if !strings.Contains(out.String(), "streamed-pre") {58 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 (
14 "strings"14 "strings"
15 "testing"15 "testing"
16 16
17- "github.com/patriceckhart/zot/packages/agent/tools"17+ "github.com/bnema/zut/packages/agent/tools"
18- "github.com/patriceckhart/zot/packages/tui"18+ "github.com/bnema/zut/packages/tui"
19 )19 )
20 20
21-func TestReadZotfileConsentKeyAcceptsOnlyYesNo(t *testing.T) {21+func TestReadZutfileConsentKeyAcceptsOnlyYesNo(t *testing.T) {
22 tests := []struct {22 tests := []struct {
23 name string23 name string
24 input []byte24 input []byte
@@ -37,7 +37,7 @@ func TestReadZotfileConsentKeyAcceptsOnlyYesNo(t *testing.T) {
37 for _, tt := range tests {37 for _, tt := range tests {
38 t.Run(tt.name, func(t *testing.T) {38 t.Run(tt.name, func(t *testing.T) {
39 var out bytes.Buffer39 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) {
41 if answer == 0 {41 if answer == 0 {
42 out.Truncate(out.Len() - 1)42 out.Truncate(out.Len() - 1)
43 return43 return
@@ -57,9 +57,9 @@ func TestReadZotfileConsentKeyAcceptsOnlyYesNo(t *testing.T) {
57 }57 }
58 }58 }
59 59
60-func TestReadZotfileConsentKeyWaitsForEnter(t *testing.T) {60+func TestReadZutfileConsentKeyWaitsForEnter(t *testing.T) {
61 var out bytes.Buffer61 var out bytes.Buffer
62- if _, err := readZotfileConsentKey(strings.NewReader("y"), func(answer byte) {62+ if _, err := readZutfileConsentKey(strings.NewReader("y"), func(answer byte) {
63 out.WriteByte(answer)63 out.WriteByte(answer)
64 }); !errors.Is(err, io.EOF) {64 }); !errors.Is(err, io.EOF) {
65 t.Fatalf("error = %v, want EOF before Enter", err)65 t.Fatalf("error = %v, want EOF before Enter", err)
@@ -69,19 +69,19 @@ func TestReadZotfileConsentKeyWaitsForEnter(t *testing.T) {
69 }69 }
70 }70 }
71 71
72-func TestReadZotfileConsentKeyInterruptsOnCtrlC(t *testing.T) {72+func TestReadZutfileConsentKeyInterruptsOnCtrlC(t *testing.T) {
73- if _, err := readZotfileConsentKey(bytes.NewReader([]byte{'x', 3, 'y'}), nil); !errors.Is(err, errZotfileConsentInterrupted) {73+ if _, err := readZutfileConsentKey(bytes.NewReader([]byte{'x', 3, 'y'}), nil); !errors.Is(err, errZutfileConsentInterrupted) {
74 t.Fatalf("error = %v, want interrupted", err)74 t.Fatalf("error = %v, want interrupted", err)
75 }75 }
76 }76 }
77 77
78-func TestReadZotfileConsentKeyReportsInputError(t *testing.T) {78+func TestReadZutfileConsentKeyReportsInputError(t *testing.T) {
79- if _, err := readZotfileConsentKey(strings.NewReader("xxx"), nil); !errors.Is(err, io.EOF) {79+ if _, err := readZutfileConsentKey(strings.NewReader("xxx"), nil); !errors.Is(err, io.EOF) {
80 t.Fatalf("error = %v, want EOF", err)80 t.Fatalf("error = %v, want EOF", err)
81 }81 }
82 }82 }
83 83
84-func writeTestZotfile(t *testing.T, manifest string) string {84+func writeTestZutfile(t *testing.T, manifest string) string {
85 t.Helper()85 t.Helper()
86 dir := t.TempDir()86 dir := t.TempDir()
87 if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(manifest), 0o600); err != nil {87 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 {
93 return dir93 return dir
94 }94 }
95 95
96-func TestLoadZotfileAllowsMissingAgentMD(t *testing.T) {96+func TestLoadZutfileAllowsMissingAgentMD(t *testing.T) {
97 dir := t.TempDir()97 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 {
99 t.Fatal(err)99 t.Fatal(err)
100 }100 }
101- zf, cleanup, err := loadZotfile(dir)101+ zf, cleanup, err := loadZutfile(dir)
102 if cleanup != nil {102 if cleanup != nil {
103 defer cleanup()103 defer cleanup()
104 }104 }
@@ -110,65 +110,65 @@ func TestLoadZotfileAllowsMissingAgentMD(t *testing.T) {
110 }110 }
111 }111 }
112 112
113-func TestValidateZotfileDirRejectsAgentMDDirectory(t *testing.T) {113+func TestValidateZutfileDirRejectsAgentMDDirectory(t *testing.T) {
114 dir := t.TempDir()114 dir := t.TempDir()
115 if err := os.Mkdir(filepath.Join(dir, "AGENT.md"), 0o700); err != nil {115 if err := os.Mkdir(filepath.Join(dir, "AGENT.md"), 0o700); err != nil {
116 t.Fatal(err)116 t.Fatal(err)
117 }117 }
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") {
119 t.Fatalf("unexpected error: %v", err)119 t.Fatalf("unexpected error: %v", err)
120 }120 }
121 }121 }
122 122
123-func TestLoadZotfileRejectsUnenforcedPermissions(t *testing.T) {123+func TestLoadZutfileRejectsUnenforcedPermissions(t *testing.T) {
124 for _, field := range []string{124 for _, field := range []string{
125 `"net":{"allow":["example.com"]}`,125 `"net":{"allow":["example.com"]}`,
126 `"env":{"read":["HOME"]}`,126 `"env":{"read":["HOME"]}`,
127 } {127 } {
128- dir := writeTestZotfile(t, `{"zotfile":1,"name":"test","permissions":{`+field+`}}`)128+ dir := writeTestZutfile(t, `{"zutfile":1,"name":"test","permissions":{`+field+`}}`)
129- if _, _, err := loadZotfile(dir); err == nil {129+ if _, _, err := loadZutfile(dir); err == nil {
130 t.Fatalf("manifest with %s was accepted", field)130 t.Fatalf("manifest with %s was accepted", field)
131 }131 }
132 }132 }
133 }133 }
134 134
135-func TestLoadZotfileBashModes(t *testing.T) {135+func TestLoadZutfileBashModes(t *testing.T) {
136 for _, mode := range []string{"none", "ask", "allowlist"} {136 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 + `"}}}`
138 if mode == "allowlist" {138 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"]}}}`
140 }140 }
141- dir := writeTestZotfile(t, manifest)141+ dir := writeTestZutfile(t, manifest)
142- if _, _, err := loadZotfile(dir); err != nil {142+ if _, _, err := loadZutfile(dir); err != nil {
143 t.Fatalf("mode %q rejected: %v", mode, err)143 t.Fatalf("mode %q rejected: %v", mode, err)
144 }144 }
145 }145 }
146- dir := writeTestZotfile(t, `{"zotfile":1,"name":"test","permissions":{"bash":{"mode":"all"}}}`)146+ dir := writeTestZutfile(t, `{"zutfile":1,"name":"test","permissions":{"bash":{"mode":"all"}}}`)
147- if _, _, err := loadZotfile(dir); err == nil || !strings.Contains(err.Error(), "unsupported bash permission mode") {147+ if _, _, err := loadZutfile(dir); err == nil || !strings.Contains(err.Error(), "unsupported bash permission mode") {
148 t.Fatalf("unexpected error for all mode: %v", err)148 t.Fatalf("unexpected error for all mode: %v", err)
149 }149 }
150- dir = writeTestZotfile(t, `{"zotfile":1,"name":"test","permissions":{"bash":{"mode":"everything"}}}`)150+ dir = writeTestZutfile(t, `{"zutfile":1,"name":"test","permissions":{"bash":{"mode":"everything"}}}`)
151- if _, _, err := loadZotfile(dir); err == nil || !strings.Contains(err.Error(), "unsupported bash permission mode") {151+ if _, _, err := loadZutfile(dir); err == nil || !strings.Contains(err.Error(), "unsupported bash permission mode") {
152 t.Fatalf("unexpected error for unknown mode: %v", err)152 t.Fatalf("unexpected error for unknown mode: %v", err)
153 }153 }
154 }154 }
155 155
156-func TestLoadZotfileRejectsUnsafeOrCollidingNames(t *testing.T) {156+func TestLoadZutfileRejectsUnsafeOrCollidingNames(t *testing.T) {
157 for _, name := range []string{"...", "Name", "two words", "a/b"} {157 for _, name := range []string{"...", "Name", "two words", "a/b"} {
158- dir := writeTestZotfile(t, `{"zotfile":1,"name":"`+name+`"}`)158+ dir := writeTestZutfile(t, `{"zutfile":1,"name":"`+name+`"}`)
159- if _, _, err := loadZotfile(dir); err == nil {159+ if _, _, err := loadZutfile(dir); err == nil {
160 t.Fatalf("unsafe manifest name %q was accepted", name)160 t.Fatalf("unsafe manifest name %q was accepted", name)
161 }161 }
162 }162 }
163 }163 }
164 164
165-func TestResolveZotfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {165+func TestResolveZutfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
166 dir := t.TempDir()166 dir := t.TempDir()
167 t.Chdir(dir)167 t.Chdir(dir)
168 if err := os.Mkdir("local-agent", 0o700); err != nil {168 if err := os.Mkdir("local-agent", 0o700); err != nil {
169 t.Fatal(err)169 t.Fatal(err)
170 }170 }
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 {
172 t.Fatal(err)172 t.Fatal(err)
173 }173 }
174 174
@@ -177,9 +177,9 @@ func TestResolveZotfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
177 want string177 want string
178 }{178 }{
179 {"local-agent", "local-agent"},179 {"local-agent", "local-agent"},
180- {"packed-agent", "packed-agent.zot"},180+ {"packed-agent", "packed-agent.zut"},
181 {"remote-agent", "remote-agent"},181 {"remote-agent", "remote-agent"},
182- {"missing.zot", "missing.zot"},182+ {"missing.zut", "missing.zut"},
183 {"agents/remote-agent", "https://github.com/agents/remote-agent"},183 {"agents/remote-agent", "https://github.com/agents/remote-agent"},
184 {`agents\remote-agent`, "https://github.com/agents/remote-agent"},184 {`agents\remote-agent`, "https://github.com/agents/remote-agent"},
185 {"frkr/zot-archify", "https://github.com/frkr/zot-archify"},185 {"frkr/zot-archify", "https://github.com/frkr/zot-archify"},
@@ -190,7 +190,7 @@ func TestResolveZotfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
190 {"https://github.com/acme/agents/example", "https://github.com/acme/agents/example"},190 {"https://github.com/acme/agents/example", "https://github.com/acme/agents/example"},
191 }191 }
192 for _, tt := range tests {192 for _, tt := range tests {
193- got, err := resolveZotfileRef(tt.ref)193+ got, err := resolveZutfileRef(tt.ref)
194 if err != nil {194 if err != nil {
195 t.Fatalf("resolve %q: %v", tt.ref, err)195 t.Fatalf("resolve %q: %v", tt.ref, err)
196 }196 }
@@ -200,8 +200,8 @@ func TestResolveZotfileRefUsesLocalFirstThenGitHubShorthand(t *testing.T) {
200 }200 }
201 }201 }
202 202
203-func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) {203+func TestLoadZutfileRejectsBundledExecutableExtension(t *testing.T) {
204- dir := writeTestZotfile(t, `{"zotfile":1,"name":"test"}`)204+ dir := writeTestZutfile(t, `{"zutfile":1,"name":"test"}`)
205 ext := filepath.Join(dir, "extensions", "bad")205 ext := filepath.Join(dir, "extensions", "bad")
206 if err := os.MkdirAll(ext, 0o700); err != nil {206 if err := os.MkdirAll(ext, 0o700); err != nil {
207 t.Fatal(err)207 t.Fatal(err)
@@ -209,31 +209,31 @@ func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) {
209 if err := os.WriteFile(filepath.Join(ext, "extension.json"), []byte(`{"name":"bad"}`), 0o600); err != nil {209 if err := os.WriteFile(filepath.Join(ext, "extension.json"), []byte(`{"name":"bad"}`), 0o600); err != nil {
210 t.Fatal(err)210 t.Fatal(err)
211 }211 }
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") {
213 t.Fatalf("unexpected error: %v", err)213 t.Fatalf("unexpected error: %v", err)
214 }214 }
215 }215 }
216 216
217-func TestCheckZotfileMinVersion(t *testing.T) {217+func TestCheckZutfileMinVersion(t *testing.T) {
218- var zf zotfileLoaded218+ var zf zutfileLoaded
219- zf.Manifest.Runtime.MinZot = "0.3.0"219+ zf.Manifest.Runtime.MinZut = "0.3.0"
220- if err := checkZotfileRequirements(zf, "0.2.75"); err == nil {220+ if err := checkZutfileRequirements(zf, "0.2.75"); err == nil {
221- t.Fatal("old zot version accepted")221+ t.Fatal("old zut version accepted")
222 }222 }
223- if err := checkZotfileRequirements(zf, "0.3.0"); err != nil {223+ if err := checkZutfileRequirements(zf, "0.3.0"); err != nil {
224 t.Fatalf("minimum version rejected: %v", err)224 t.Fatalf("minimum version rejected: %v", err)
225 }225 }
226 }226 }
227 227
228-func TestApplyZotfileModelRequirementsRejectsUnsupportedFields(t *testing.T) {228+func TestApplyZutfileModelRequirementsRejectsUnsupportedFields(t *testing.T) {
229- var m ZotfileManifest229+ var m ZutfileManifest
230 m.Model.MinTier = "frontier"230 m.Model.MinTier = "frontier"
231- if err := applyZotfileModelRequirements(&Args{}, m); err == nil {231+ if err := applyZutfileModelRequirements(&Args{}, m); err == nil {
232 t.Fatal("unsupported min_tier was ignored")232 t.Fatal("unsupported min_tier was ignored")
233 }233 }
234 m.Model.MinTier = ""234 m.Model.MinTier = ""
235 m.Model.Requires = []string{"audio"}235 m.Model.Requires = []string{"audio"}
236- if err := applyZotfileModelRequirements(&Args{}, m); err == nil {236+ if err := applyZutfileModelRequirements(&Args{}, m); err == nil {
237 t.Fatal("unsupported capability was ignored")237 t.Fatal("unsupported capability was ignored")
238 }238 }
239 }239 }
@@ -251,7 +251,7 @@ func TestUntarRejectsTraversalAndOversizedEntry(t *testing.T) {
251 if err := untar(bytes.NewReader(makeTar("../escape", 0)), t.TempDir()); err == nil {251 if err := untar(bytes.NewReader(makeTar("../escape", 0)), t.TempDir()); err == nil {
252 t.Fatal("path traversal accepted")252 t.Fatal("path traversal accepted")
253 }253 }
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 {
255 t.Fatal("oversized entry accepted")255 t.Fatal("oversized entry accepted")
256 }256 }
257 }257 }
@@ -281,13 +281,13 @@ func TestParseGitHubAgentURL(t *testing.T) {
281 }281 }
282 }282 }
283 283
284-func TestLoadRemoteZotfileDownloadsTemporaryGitHubArchive(t *testing.T) {284+func TestLoadRemoteZutfileDownloadsTemporaryGitHubArchive(t *testing.T) {
285 var archive bytes.Buffer285 var archive bytes.Buffer
286 gz := gzip.NewWriter(&archive)286 gz := gzip.NewWriter(&archive)
287 tw := tar.NewWriter(gz)287 tw := tar.NewWriter(gz)
288 files := map[string]string{288 files := map[string]string{
289- "agents-main/zot-maintenance/manifest.json": `{"zotfile":1,"name":"zot-maintenance"}`,289+ "agents-main/zut-maintenance/manifest.json": `{"zutfile":1,"name":"zut-maintenance"}`,
290- "agents-main/zot-maintenance/AGENT.md": "Maintain zot.",290+ "agents-main/zut-maintenance/AGENT.md": "Maintain zut.",
291 }291 }
292 for name, content := range files {292 for name, content := range files {
293 if err := tw.WriteHeader(&tar.Header{Name: name, Mode: 0o600, Size: int64(len(content))}); err != nil {293 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) {
313 githubArchiveURL = func(_, _, _ string) string { return server.URL }313 githubArchiveURL = func(_, _, _ string) string { return server.URL }
314 t.Cleanup(func() { githubArchiveURL = oldArchiveURL })314 t.Cleanup(func() { githubArchiveURL = oldArchiveURL })
315 315
316- u, _ := url.Parse("https://github.com/acme/agents/zot-maintenance")316+ u, _ := url.Parse("https://github.com/acme/agents/zut-maintenance")
317- zf, cleanup, err := loadRemoteZotfile(u)317+ zf, cleanup, err := loadRemoteZutfile(u)
318 if err != nil {318 if err != nil {
319 t.Fatal(err)319 t.Fatal(err)
320 }320 }
321- if zf.Manifest.Name != "zot-maintenance" || !zf.Temp {321+ if zf.Manifest.Name != "zut-maintenance" || !zf.Temp {
322- t.Fatalf("unexpected zotfile: %+v", zf)322+ t.Fatalf("unexpected zutfile: %+v", zf)
323 }323 }
324 root := filepath.Dir(filepath.Dir(zf.Dir))324 root := filepath.Dir(filepath.Dir(zf.Dir))
325 cleanup()325 cleanup()
@@ -328,19 +328,19 @@ func TestLoadRemoteZotfileDownloadsTemporaryGitHubArchive(t *testing.T) {
328 }328 }
329 }329 }
330 330
331-func TestFormatZotfileConsentUsesThemeColors(t *testing.T) {331+func TestFormatZutfileConsentUsesThemeColors(t *testing.T) {
332 th := tui.Theme{332 th := tui.Theme{
333 FG: tui.Color256(1), Muted: tui.Color256(2), Accent: tui.Color256(3),333 FG: tui.Color256(1), Muted: tui.Color256(2), Accent: tui.Color256(3),
334 Assistant: tui.Color256(4), Warning: tui.Color256(5),334 Assistant: tui.Color256(4), Warning: tui.Color256(5),
335 }335 }
336- zf := zotfileLoaded{}336+ zf := zutfileLoaded{}
337 zf.Manifest.Name = "reviewer"337 zf.Manifest.Name = "reviewer"
338 zf.Manifest.Version = "1.2.3"338 zf.Manifest.Version = "1.2.3"
339 perms := tools.PermissionSet{}339 perms := tools.PermissionSet{}
340 perms.FS.Read = []string{"/workspace"}340 perms.FS.Read = []string{"/workspace"}
341 perms.Bash.Mode = "ask"341 perms.Bash.Mode = "ask"
342 342
343- got := formatZotfileConsent(zf, perms, th, true)343+ got := formatZutfileConsent(zf, perms, th, true)
344 for _, want := range []string{344 for _, want := range []string{
345 th.FGColor(th.Assistant, "Agent"),345 th.FGColor(th.Assistant, "Agent"),
346 th.FGColor(th.Accent, "reviewer@1.2.3"),346 th.FGColor(th.Accent, "reviewer@1.2.3"),
@@ -377,13 +377,13 @@ func TestPeelLeadingYes(t *testing.T) {
377 }377 }
378 }378 }
379 379
380-func TestConsentZotfileAutoYesWritesReceipt(t *testing.T) {380+func TestConsentZutfileAutoYesWritesReceipt(t *testing.T) {
381 home := t.TempDir()381 home := t.TempDir()
382- t.Setenv("ZOT_HOME", home)382+ t.Setenv("ZUT_HOME", home)
383- zf := zotfileLoaded{Digest: "abc123", Manifest: ZotfileManifest{Name: "demo", Version: "1.0.0"}}383+ zf := zutfileLoaded{Digest: "abc123", Manifest: ZutfileManifest{Name: "demo", Version: "1.0.0"}}
384 var perms tools.PermissionSet384 var perms tools.PermissionSet
385 perms.Bash.Mode = "none"385 perms.Bash.Mode = "none"
386- allowed, err := consentZotfile(zf, perms, true)386+ allowed, err := consentZutfile(zf, perms, true)
387 if err != nil || !allowed {387 if err != nil || !allowed {
388 t.Fatalf("auto yes: allowed=%v err=%v", allowed, err)388 t.Fatalf("auto yes: allowed=%v err=%v", allowed, err)
389 }389 }
@@ -392,19 +392,19 @@ func TestConsentZotfileAutoYesWritesReceipt(t *testing.T) {
392 t.Fatalf("consent receipt missing: %v", err)392 t.Fatalf("consent receipt missing: %v", err)
393 }393 }
394 // Second call without -y should use the receipt.394 // Second call without -y should use the receipt.
395- allowed, err = consentZotfile(zf, perms, false)395+ allowed, err = consentZutfile(zf, perms, false)
396 if err != nil || !allowed {396 if err != nil || !allowed {
397 t.Fatalf("cached consent: allowed=%v err=%v", allowed, err)397 t.Fatalf("cached consent: allowed=%v err=%v", allowed, err)
398 }398 }
399 }399 }
400 400
401-func TestConsentZotfileAutoYesAskDoesNotCache(t *testing.T) {401+func TestConsentZutfileAutoYesAskDoesNotCache(t *testing.T) {
402 home := t.TempDir()402 home := t.TempDir()
403- t.Setenv("ZOT_HOME", home)403+ t.Setenv("ZUT_HOME", home)
404- zf := zotfileLoaded{Digest: "askdig", Manifest: ZotfileManifest{Name: "ask-agent", Version: "1.0.0"}}404+ zf := zutfileLoaded{Digest: "askdig", Manifest: ZutfileManifest{Name: "ask-agent", Version: "1.0.0"}}
405 var perms tools.PermissionSet405 var perms tools.PermissionSet
406 perms.Bash.Mode = "ask"406 perms.Bash.Mode = "ask"
407- if _, err := consentZotfile(zf, perms, true); err != nil {407+ if _, err := consentZutfile(zf, perms, true); err != nil {
408 t.Fatal(err)408 t.Fatal(err)
409 }409 }
410 path := filepath.Join(home, "agents", "ask-agent", "consents", "askdig.json")410 path := filepath.Join(home, "agents", "ask-agent", "consents", "askdig.json")
M packages/core/agent.go +1 -1
@@ -10,7 +10,7 @@ import (
10 "time"10 "time"
11 "unicode/utf8"11 "unicode/utf8"
12 12
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 // Agent is a stateful conversation bound to a provider client, a model,16 // 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
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/provider"6+ "github.com/bnema/zut/packages/provider"
7 )7 )
8 8
9 func TestAppendUserContextAddsAndPublishesMessage(t *testing.T) {9 func TestAppendUserContextAddsAndPublishesMessage(t *testing.T) {
M packages/core/agent_retry_test.go +1 -1
@@ -10,7 +10,7 @@ import (
10 "time"10 "time"
11 "unicode/utf8"11 "unicode/utf8"
12 12
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 type retryFakeClient struct {16 type retryFakeClient struct {
M packages/core/compact.go +1 -1
@@ -7,7 +7,7 @@ import (
7 "strings"7 "strings"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 // Compact summarizes the agent's transcript via the LLM and replaces13 // Compact summarizes the agent's transcript via the LLM and replaces
M packages/core/core_test.go +2 -2
@@ -7,7 +7,7 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 func TestAgentRejectsEmptyPrompt(t *testing.T) {13 func TestAgentRejectsEmptyPrompt(t *testing.T) {
@@ -42,7 +42,7 @@ func TestAgentSetPromptConfigSwapsPromptAndToolsAtomically(t *testing.T) {
42 42
43 func TestSessionRoundTrip(t *testing.T) {43 func TestSessionRoundTrip(t *testing.T) {
44 dir := t.TempDir()44 dir := t.TempDir()
45- os.Setenv("ZOT_HOME", dir)45+ os.Setenv("ZUT_HOME", dir)
46 46
47 sess, err := NewSession(dir, "/tmp/project", "anthropic", "claude-sonnet-4-5", "test")47 sess, err := NewSession(dir, "/tmp/project", "anthropic", "claude-sonnet-4-5", "test")
48 if err != nil {48 if err != nil {
M packages/core/cost.go +1 -1
@@ -1,6 +1,6 @@
1 package core1 package core
2 2
3-import "github.com/patriceckhart/zot/packages/provider"3+import "github.com/bnema/zut/packages/provider"
4 4
5 // CostTracker accumulates usage across turns in a session.5 // CostTracker accumulates usage across turns in a session.
6 //6 //
M packages/core/events.go +1 -1
@@ -3,7 +3,7 @@ package core
3 import (3 import (
4 "encoding/json"4 "encoding/json"
5 5
6- "github.com/patriceckhart/zot/packages/provider"6+ "github.com/bnema/zut/packages/provider"
7 )7 )
8 8
9 // AgentEvent is the superset of events emitted by an Agent run.9 // 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 (
5 "strings"5 "strings"
6 "testing"6 "testing"
7 7
8- "github.com/patriceckhart/zot/packages/provider"8+ "github.com/bnema/zut/packages/provider"
9 )9 )
10 10
11 type fastModeTestClient struct {11 type fastModeTestClient struct {
M packages/core/intercept_test.go +1 -1
@@ -5,7 +5,7 @@ import (
5 "encoding/json"5 "encoding/json"
6 "testing"6 "testing"
7 7
8- "github.com/patriceckhart/zot/packages/provider"8+ "github.com/bnema/zut/packages/provider"
9 )9 )
10 10
11 // recordingTool captures the args it was invoked with so the test11 // recordingTool captures the args it was invoked with so the test
M packages/core/queue_test.go +1 -1
@@ -7,7 +7,7 @@ import (
7 "sync/atomic"7 "sync/atomic"
8 "testing"8 "testing"
9 9
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 type queueFakeClient struct {13 type queueFakeClient struct {
M packages/core/session.go +4 -4
@@ -16,8 +16,8 @@ import (
16 "strings"16 "strings"
17 "time"17 "time"
18 18
19+ "github.com/bnema/zut/packages/provider"
19 "github.com/google/uuid"20 "github.com/google/uuid"
20- "github.com/patriceckhart/zot/packages/provider"
21 )21 )
22 22
23 // Session is a JSONL-backed conversation transcript tied to a cwd.23 // Session is a JSONL-backed conversation transcript tied to a cwd.
@@ -782,7 +782,7 @@ func OpenSession(path string) (*Session, []provider.Message, error) {
782 //782 //
783 // Corruption gets into the transcript two ways we know of:783 // Corruption gets into the transcript two ways we know of:
784 //784 //
785-// - Older zot builds that persisted the assistant tool_use row785+// - Older zut builds that persisted the assistant tool_use row
786 // before the tool_result row, then crashed between the two.786 // before the tool_result row, then crashed between the two.
787 // - Abort paths in older builds that didn't drop the mid-turn787 // - Abort paths in older builds that didn't drop the mid-turn
788 // assistant message cleanly.788 // assistant message cleanly.
@@ -1025,7 +1025,7 @@ func firstTextFromMessage(msg provider.Message) string {
1025 1025
1026 // PruneEmptySessions deletes session files in cwd's session directory1026 // PruneEmptySessions deletes session files in cwd's session directory
1027 // that contain only a meta line (no messages were ever appended).1027 // that contain only a meta line (no messages were ever appended).
1028-// Cleans up the backlog of empty stubs created by old zot versions1028+// Cleans up the backlog of empty stubs created by old zut versions
1029 // that wrote a meta line at NewSession time and never followed up.1029 // that wrote a meta line at NewSession time and never followed up.
1030 // Errors are swallowed; the caller treats this as best-effort.1030 // Errors are swallowed; the caller treats this as best-effort.
1031 func PruneEmptySessions(root, cwd string) {1031 func PruneEmptySessions(root, cwd string) {
@@ -1255,7 +1255,7 @@ func (s *Session) AppendExtensionState(extension string, state json.RawMessage)
1255 1255
1256 // Close flushes and closes the session file. If the session was1256 // Close flushes and closes the session file. If the session was
1257 // freshly created in this process and never had any messages1257 // freshly created in this process and never had any messages
1258-// appended (the user opened zot, looked around, and exited without1258+// appended (the user opened zut, looked around, and exited without
1259 // prompting), the file is deleted on close so the sessions list1259 // prompting), the file is deleted on close so the sessions list
1260 // doesn't fill up with empty meta-only stubs.1260 // doesn't fill up with empty meta-only stubs.
1261 func (s *Session) Close() error {1261 func (s *Session) Close() error {
M packages/core/session_deferred_tools_test.go +1 -1
@@ -3,7 +3,7 @@ package core
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/provider"6+ "github.com/bnema/zut/packages/provider"
7 )7 )
8 8
9 func TestSessionPreservesActivatedDeferredTools(t *testing.T) {9 func TestSessionPreservesActivatedDeferredTools(t *testing.T) {
M packages/core/session_history_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "testing"4 "testing"
5 "time"5 "time"
6 6
7- "github.com/patriceckhart/zot/packages/provider"7+ "github.com/bnema/zut/packages/provider"
8 )8 )
9 9
10 func TestReadSessionHistoryRetainsPreCompactionSegments(t *testing.T) {10 func TestReadSessionHistoryRetainsPreCompactionSegments(t *testing.T) {
M packages/core/session_portable.go +10 -10
@@ -13,19 +13,19 @@ import (
13 "strings"13 "strings"
14 "time"14 "time"
15 15
16+ "github.com/bnema/zut/packages/provider"
16 "github.com/google/uuid"17 "github.com/google/uuid"
17- "github.com/patriceckhart/zot/packages/provider"
18 )18 )
19 19
20 // PortableExt is the filesystem extension used for exported sessions.20 // PortableExt is the filesystem extension used for exported sessions.
21-// A ".zotsession" is just a zot JSONL session file with the meta21+// A ".zutsession" is just a zut JSONL session file with the meta
22 // header rewritten so the importing user gets fresh ownership.22 // header rewritten so the importing user gets fresh ownership.
23-const PortableExt = ".zotsession"23+const PortableExt = ".zutsession"
24 24
25 // ExportSession writes the session at srcPath to dstPath as a25 // ExportSession writes the session at srcPath to dstPath as a
26-// portable .zotsession file. If dstPath is an existing directory the26+// portable .zutsession file. If dstPath is an existing directory the
27 // file is created inside it with a name derived from the session's27 // file is created inside it with a name derived from the session's
28-// meta ("YYYYMMDD-HHMMSS-<first-prompt-excerpt>.zotsession"). The28+// meta ("YYYYMMDD-HHMMSS-<first-prompt-excerpt>.zutsession"). The
29 // destination's directory is created if needed. Returns the final29 // destination's directory is created if needed. Returns the final
30 // resolved path so the caller can tell the user where it landed.30 // resolved path so the caller can tell the user where it landed.
31 //31 //
@@ -72,7 +72,7 @@ func ExportSession(srcPath, dstPath string) (string, error) {
72 72
73 // Scan the rest of the file for the first user message so we can73 // Scan the rest of the file for the first user message so we can
74 // build a humane filename. Only reads if dstPath doesn't already74 // build a humane filename. Only reads if dstPath doesn't already
75- // end in .zotsession.75+ // end in .zutsession.
76 firstPrompt := ""76 firstPrompt := ""
77 if !strings.HasSuffix(strings.ToLower(dstPath), PortableExt) {77 if !strings.HasSuffix(strings.ToLower(dstPath), PortableExt) {
78 if fi, _ := os.Stat(dstPath); fi == nil || fi.IsDir() {78 if fi, _ := os.Stat(dstPath); fi == nil || fi.IsDir() {
@@ -156,13 +156,13 @@ func ExportSession(srcPath, dstPath string) (string, error) {
156 return outPath, nil156 return outPath, nil
157 }157 }
158 158
159-// ImportSession copies the .zotsession file at srcPath into the159+// ImportSession copies the .zutsession file at srcPath into the
160 // running user's session store under the given root+cwd, rewriting160 // running user's session store under the given root+cwd, rewriting
161 // the meta's id / cwd / started fields so the imported session is161 // the meta's id / cwd / started fields so the imported session is
162 // owned by the current user / directory / clock. Returns the path162 // owned by the current user / directory / clock. Returns the path
163 // of the created session file, ready to pass to OpenSession.163 // of the created session file, ready to pass to OpenSession.
164 //164 //
165-// The imported session is a first-class zot session: it'll show up165+// The imported session is a first-class zut session: it'll show up
166 // in /sessions, /jump, and on-disk summaries just like any other.166 // in /sessions, /jump, and on-disk summaries just like any other.
167 // Messages and usage rows are preserved verbatim.167 // Messages and usage rows are preserved verbatim.
168 func ImportSession(srcPath, root, cwd, version string) (string, error) {168 func ImportSession(srcPath, root, cwd, version string) (string, error) {
@@ -434,7 +434,7 @@ func writeBranchSession(root, cwd, version string, parent SessionMeta, messages
434 // Build in a same-directory temporary file. Rename is atomic only when434 // Build in a same-directory temporary file. Rename is atomic only when
435 // the temporary and final paths share a directory; closing the file first435 // the temporary and final paths share a directory; closing the file first
436 // also ensures no buffered writer or descriptor can expose a partial branch.436 // 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")
438 if err != nil {438 if err != nil {
439 return "", fmt.Errorf("branch: create temp: %w", err)439 return "", fmt.Errorf("branch: create temp: %w", err)
440 }440 }
@@ -910,7 +910,7 @@ func firstUserPrompt(src io.Reader) (string, error) {
910 }910 }
911 }911 }
912 912
913-// filenameFor builds a descriptive .zotsession filename from the913+// filenameFor builds a descriptive .zutsession filename from the
914 // session's start time and, when available, an excerpt of the914 // session's start time and, when available, an excerpt of the
915 // first user prompt.915 // first user prompt.
916 func filenameFor(started time.Time, id, firstPrompt string) string {916 func filenameFor(started time.Time, id, firstPrompt string) string {
M packages/core/session_portable_test.go +4 -4
@@ -7,7 +7,7 @@ import (
7 "strings"7 "strings"
8 "testing"8 "testing"
9 9
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 // TestSessionExportImportRoundTrip writes a few messages to a live13 // TestSessionExportImportRoundTrip writes a few messages to a live
@@ -159,7 +159,7 @@ func TestScanSessionMetaReturnsLatestRowWithoutHydratingMessages(t *testing.T) {
159 }159 }
160 160
161 // TestExportToFilePath writes to an explicit file path (no directory161 // 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.
163 func TestExportToFilePath(t *testing.T) {163 func TestExportToFilePath(t *testing.T) {
164 root := t.TempDir()164 root := t.TempDir()
165 sess, err := NewSession(root, "/cwd", "anthropic", "claude-opus-4-7", "0.0.0-test")165 sess, err := NewSession(root, "/cwd", "anthropic", "claude-opus-4-7", "0.0.0-test")
@@ -172,14 +172,14 @@ func TestExportToFilePath(t *testing.T) {
172 })172 })
173 _ = sess.Close()173 _ = sess.Close()
174 174
175- // No extension — should add .zotsession.175+ // No extension — should add .zutsession.
176 dst := filepath.Join(t.TempDir(), "mysession")176 dst := filepath.Join(t.TempDir(), "mysession")
177 out, err := ExportSession(sess.Path, dst)177 out, err := ExportSession(sess.Path, dst)
178 if err != nil {178 if err != nil {
179 t.Fatal(err)179 t.Fatal(err)
180 }180 }
181 if !strings.HasSuffix(out, PortableExt) {181 if !strings.HasSuffix(out, PortableExt) {
182- t.Errorf("want .zotsession suffix on %q", out)182+ t.Errorf("want .zutsession suffix on %q", out)
183 }183 }
184 }184 }
185 185
M packages/core/session_repair_test.go +1 -1
@@ -3,7 +3,7 @@ package core
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/patriceckhart/zot/packages/provider"6+ "github.com/bnema/zut/packages/provider"
7 )7 )
8 8
9 // TestRepairToolUseResultPairsAppendsStub covers the most common9 // TestRepairToolUseResultPairsAppendsStub covers the most common
M packages/core/session_snapshot_test.go +1 -1
@@ -10,7 +10,7 @@ import (
10 "testing"10 "testing"
11 "time"11 "time"
12 12
13- "github.com/patriceckhart/zot/packages/provider"13+ "github.com/bnema/zut/packages/provider"
14 )14 )
15 15
16 func TestAppendMessageRejectsEmptyContent(t *testing.T) {16 func TestAppendMessageRejectsEmptyContent(t *testing.T) {
M packages/core/session_usage_test.go +1 -1
@@ -4,7 +4,7 @@ import (
4 "testing"4 "testing"
5 "time"5 "time"
6 6
7- "github.com/patriceckhart/zot/packages/provider"7+ "github.com/bnema/zut/packages/provider"
8 )8 )
9 9
10 func TestSessionUsageDetailResetsContextAfterCompaction(t *testing.T) {10 func TestSessionUsageDetailResetsContextAfterCompaction(t *testing.T) {
M packages/core/title.go +1 -1
@@ -6,7 +6,7 @@ import (
6 "strings"6 "strings"
7 "unicode"7 "unicode"
8 8
9- "github.com/patriceckhart/zot/packages/provider"9+ "github.com/bnema/zut/packages/provider"
10 )10 )
11 11
12 // SessionTitleMaxChars is the maximum length of an automatically generated12 // SessionTitleMaxChars is the maximum length of an automatically generated
M packages/core/title_test.go +1 -1
@@ -7,7 +7,7 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/patriceckhart/zot/packages/provider"10+ "github.com/bnema/zut/packages/provider"
11 )11 )
12 12
13 type titleTestClient struct {13 type titleTestClient struct {
M packages/core/tool.go +1 -1
@@ -9,7 +9,7 @@ import (
9 "fmt"9 "fmt"
10 "sort"10 "sort"
11 11
12- "github.com/patriceckhart/zot/packages/provider"12+ "github.com/bnema/zut/packages/provider"
13 )13 )
14 14
15 // Tool is a capability the agent can invoke.15 // Tool is a capability the agent can invoke.
M packages/ignore/gitignore.go +1 -1
@@ -1,5 +1,5 @@
1 // Package ignore provides a minimal .gitignore matcher shared across1 // Package ignore provides a minimal .gitignore matcher shared across
2-// zot. It is intentionally small: enough to drop obvious non-source2+// zut. It is intentionally small: enough to drop obvious non-source
3 // directories (build outputs, dependency and tool caches) from3 // directories (build outputs, dependency and tool caches) from
4 // recursive walks, not a faithful git reimplementation.4 // recursive walks, not a faithful git reimplementation.
5 package ignore5 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,
610 msg := strings.TrimSpace(string(b))610 msg := strings.TrimSpace(string(b))
611 // A 403 on the bearer route is almost always a region mismatch:611 // A 403 on the bearer route is almost always a region mismatch:
612 // short-term Bedrock API keys are scoped to the region of the612 // short-term Bedrock API keys are scoped to the region of the
613- // console session that minted them, but zot defaults to613+ // console session that minted them, but zut defaults to
614 // us-east-1. Surface the resolved region and the fix so the user614 // us-east-1. Surface the resolved region and the fix so the user
615 // is not left guessing why a freshly-copied key is "invalid".615 // is not left guessing why a freshly-copied key is "invalid".
616 if resp.StatusCode == http.StatusForbidden && c.bearerToken != "" {616 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
535 return nil, err535 return nil, err
536 }536 }
537 537
538- // Optional debug dump: when $ZOT_DEBUG_ANTHROPIC is a file path538+ // Optional debug dump: when $ZUT_DEBUG_ANTHROPIC is a file path
539 // we append every outgoing request body to it, one JSON object539 // we append every outgoing request body to it, one JSON object
540 // per line. Useful for diffing turn N vs turn N+1 to understand540 // per line. Useful for diffing turn N vs turn N+1 to understand
541 // why the cache prefix isn't matching.541 // 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 != "" {
543 if f, derr := os.OpenFile(dump, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); derr == nil {543 if f, derr := os.OpenFile(dump, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); derr == nil {
544 _, _ = f.Write(body)544 _, _ = f.Write(body)
545 _, _ = f.Write([]byte{'\n'})545 _, _ = f.Write([]byte{'\n'})
M packages/provider/anthropic_image.go +1 -1
@@ -17,7 +17,7 @@ import (
17 17
18 // anthMaxImageSide is Anthropic's per-image dimension cap when a18 // anthMaxImageSide is Anthropic's per-image dimension cap when a
19 // request contains more than one image. Single-image requests are19 // request contains more than one image. Single-image requests are
20-// allowed up to 8000 px, but zot conversations routinely include20+// allowed up to 8000 px, but zut conversations routinely include
21 // multiple images (a screenshot here, a tool result image there)21 // multiple images (a screenshot here, a tool result image there)
22 // so we always normalise down to the stricter limit. 2000 px on the22 // so we always normalise down to the stricter limit. 2000 px on the
23 // longest side is well below their cap and still readable for the23 // 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 (
10 )10 )
11 11
12 func TestAPIKeyCommandHelperProcess(t *testing.T) {12 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" {
14 return14 return
15 }15 }
16 args := os.Args16 args := os.Args
@@ -22,9 +22,9 @@ func TestAPIKeyCommandHelperProcess(t *testing.T) {
22 }22 }
23 switch args[1] {23 switch args[1] {
24 case "print":24 case "print":
25- fmt.Print(os.Getenv("ZOT_API_KEY_COMMAND_VALUE"))25+ fmt.Print(os.Getenv("ZUT_API_KEY_COMMAND_VALUE"))
26 case "fail":26 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"))
28 os.Exit(1)28 os.Exit(1)
29 case "sleep":29 case "sleep":
30 time.Sleep(5 * time.Second)30 time.Sleep(5 * time.Second)
@@ -71,8 +71,8 @@ func resetAPIKeyCommandCache(t *testing.T) {
71 71
72 func TestResolveAPIKeyCommand(t *testing.T) {72 func TestResolveAPIKeyCommand(t *testing.T) {
73 resetAPIKeyCommandCache(t)73 resetAPIKeyCommandCache(t)
74- t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")74+ t.Setenv("ZUT_API_KEY_COMMAND_HELPER", "1")
75- t.Setenv("ZOT_API_KEY_COMMAND_VALUE", "secret-value\r\n")75+ t.Setenv("ZUT_API_KEY_COMMAND_VALUE", "secret-value\r\n")
76 76
77 got, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("print"))77 got, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("print"))
78 if err != nil {78 if err != nil {
@@ -85,7 +85,7 @@ func TestResolveAPIKeyCommand(t *testing.T) {
85 85
86 func TestResolveAPIKeyCommandCachesSuccess(t *testing.T) {86 func TestResolveAPIKeyCommandCachesSuccess(t *testing.T) {
87 resetAPIKeyCommandCache(t)87 resetAPIKeyCommandCache(t)
88- t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")88+ t.Setenv("ZUT_API_KEY_COMMAND_HELPER", "1")
89 countPath := t.TempDir() + string(os.PathSeparator) + "count"89 countPath := t.TempDir() + string(os.PathSeparator) + "count"
90 command := apiKeyHelperCommand("count", countPath)90 command := apiKeyHelperCommand("count", countPath)
91 91
@@ -109,8 +109,8 @@ func TestResolveAPIKeyCommandCachesSuccess(t *testing.T) {
109 109
110 func TestResolveAPIKeyCommandFailureDoesNotExposeOutput(t *testing.T) {110 func TestResolveAPIKeyCommandFailureDoesNotExposeOutput(t *testing.T) {
111 resetAPIKeyCommandCache(t)111 resetAPIKeyCommandCache(t)
112- t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")112+ t.Setenv("ZUT_API_KEY_COMMAND_HELPER", "1")
113- t.Setenv("ZOT_API_KEY_COMMAND_VALUE", "must-not-appear")113+ t.Setenv("ZUT_API_KEY_COMMAND_VALUE", "must-not-appear")
114 114
115 _, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("fail"))115 _, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("fail"))
116 if err == nil {116 if err == nil {
@@ -123,7 +123,7 @@ func TestResolveAPIKeyCommandFailureDoesNotExposeOutput(t *testing.T) {
123 123
124 func TestResolveAPIKeyCommandTimeoutAndOutputLimit(t *testing.T) {124 func TestResolveAPIKeyCommandTimeoutAndOutputLimit(t *testing.T) {
125 resetAPIKeyCommandCache(t)125 resetAPIKeyCommandCache(t)
126- t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")126+ t.Setenv("ZUT_API_KEY_COMMAND_HELPER", "1")
127 127
128 t.Run("timeout", func(t *testing.T) {128 t.Run("timeout", func(t *testing.T) {
129 command := apiKeyHelperCommand("sleep")129 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.1+// Package assets holds static resources embedded in the zut binary.
2-// Currently just the zot logo used by the tui welcome banner.2+// Currently just the zut logo used by the tui welcome banner.
3 package assets3 package assets
4 4
5 import _ "embed"5 import _ "embed"
6 6
7-// LogoPNG is the pixel-art zot `z` logo as PNG bytes.7+// LogoPNG is the pixel-art zut `z` logo as PNG bytes.
8 // Used by the interactive welcome banner; decoded once and rasterized8 // Used by the interactive welcome banner; decoded once and rasterized
9 // to Unicode half-blocks so it renders on any terminal without needing9 // to Unicode half-blocks so it renders on any terminal without needing
10 // inline image support.10 // inline image support.
11 //11 //
12-//go:embed zot-logo.png12+//go:embed zut-logo.png
13 var LogoPNG []byte13 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 (
10 "sync"10 "sync"
11 "time"11 "time"
12 12
13- "github.com/patriceckhart/zot/packages/provider/auth/assets"13+ "github.com/bnema/zut/packages/provider/auth/assets"
14 )14 )
15 15
16 // CallbackResult is what an OAuth callback server returns once the16 // CallbackResult is what an OAuth callback server returns once the
@@ -40,7 +40,7 @@ func NewCallbackServer(p OAuthProvider, expectedState string) (*CallbackServer,
40 addr := fmt.Sprintf("%s:%d", p.RedirectHost, p.RedirectPort)40 addr := fmt.Sprintf("%s:%d", p.RedirectHost, p.RedirectPort)
41 l, err := net.Listen("tcp", addr)41 l, err := net.Listen("tcp", addr)
42 if err != nil {42 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)
44 }44 }
45 cs := &CallbackServer{45 cs := &CallbackServer{
46 l: l,46 l: l,
@@ -123,9 +123,9 @@ func (cs *CallbackServer) deliver(r CallbackResult) {
123 123
124 // ---- static HTML for the callback tab ----124 // ---- static HTML for the callback tab ----
125 125
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:
127 // near-black background (#0a0a0a), white text, Geist Mono type, cyan127 // near-black background (#0a0a0a), white text, Geist Mono type, cyan
128-// accent on the word "zot". Serves every step of both the api-key128+// accent on the word "zut". Serves every step of both the api-key
129 // flow and the oauth callback flow.129 // flow and the oauth callback flow.
130 const monoStyle = `<style>130 const monoStyle = `<style>
131 @import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;600&display=swap');131 @import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;600&display=swap');
@@ -155,7 +155,7 @@ const monoStyle = `<style>
155 letter-spacing: 0.01em;155 letter-spacing: 0.01em;
156 }156 }
157 h1 .mark { display: inline-block; width: 1.25rem; }157 h1 .mark { display: inline-block; width: 1.25rem; }
158- .zot { color: #7ed3fc; }158+ .zut { color: #7ed3fc; }
159 .rule { border: 0; border-top: 1px solid #ffffff; margin: 1.5rem 0; }159 .rule { border: 0; border-top: 1px solid #ffffff; margin: 1.5rem 0; }
160 .muted { color: #9ca3af; }160 .muted { color: #9ca3af; }
161 .mono { font-family: inherit; word-break: break-all; }161 .mono { font-family: inherit; word-break: break-all; }
@@ -179,9 +179,9 @@ const monoStyle = `<style>
179 button:hover { background: #0a0a0a; color: #ffffff; }179 button:hover { background: #0a0a0a; color: #ffffff; }
180 </style>`180 </style>`
181 181
182-// logoTag is the <img> element used at the top of every zot-served182+// logoTag is the <img> element used at the top of every zut-served
183 // page. The image bytes are served from /logo.png by the same server.183 // 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" />`
185 185
186 // serveLogo writes the embedded PNG with appropriate caching headers.186 // serveLogo writes the embedded PNG with appropriate caching headers.
187 // Shared between the oauth callback server and the api-key login server.187 // Shared between the oauth callback server and the api-key login server.
@@ -193,21 +193,21 @@ func serveLogo(w http.ResponseWriter, r *http.Request) {
193 193
194 func oauthSuccessHTML(provider string) string {194 func oauthSuccessHTML(provider string) string {
195 p := strings.ToLower(provider)195 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>
197 ` + logoTag + `197 ` + logoTag + `
198 <h1><span class="mark">✓</span> logged in to ` + p + `</h1>198 <h1><span class="mark">✓</span> logged in to ` + p + `</h1>
199 <hr class="rule">199 <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>
201 </body></html>`201 </body></html>`
202 }202 }
203 203
204 func oauthErrorHTML(msg string) string {204 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>
206 ` + logoTag + `206 ` + logoTag + `
207 <h1><span class="mark">✗</span> login failed</h1>207 <h1><span class="mark">✗</span> login failed</h1>
208 <hr class="rule">208 <hr class="rule">
209 <p class="msg mono">` + htmlEscape(msg) + `</p>209 <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>
211 </body></html>`211 </body></html>`
212 }212 }
213 213
M packages/provider/auth/kimi.go +2 -2
@@ -30,7 +30,7 @@ func RequestKimiDeviceAuthorization(ctx context.Context) (KimiDeviceAuthorizatio
30 }30 }
31 req.Header.Set("content-type", "application/x-www-form-urlencoded")31 req.Header.Set("content-type", "application/x-www-form-urlencoded")
32 req.Header.Set("accept", "application/json")32 req.Header.Set("accept", "application/json")
33- req.Header.Set("user-agent", "zot")33+ req.Header.Set("user-agent", "zut")
34 34
35 resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)35 resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
36 if err != nil {36 if err != nil {
@@ -77,7 +77,7 @@ func pollKimiDeviceTokenOnce(ctx context.Context, deviceCode string) (*OAuthToke
77 }77 }
78 req.Header.Set("content-type", "application/x-www-form-urlencoded")78 req.Header.Set("content-type", "application/x-www-form-urlencoded")
79 req.Header.Set("accept", "application/json")79 req.Header.Set("accept", "application/json")
80- req.Header.Set("user-agent", "zot")80+ req.Header.Set("user-agent", "zut")
81 resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)81 resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
82 if err != nil {82 if err != nil {
83 return nil, 0, fmt.Errorf("kimi token poll: %w", err)83 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) {
453 // (containers, SSH without display forwarding, etc.) instead of453 // (containers, SSH without display forwarding, etc.) instead of
454 // trying to bind a callback port the user can never reach.454 // trying to bind a callback port the user can never reach.
455 func HasBrowser() bool {455 func HasBrowser() bool {
456- if os.Getenv("ZOT_NO_BROWSER") != "" {456+ if os.Getenv("ZUT_NO_BROWSER") != "" {
457 return false457 return false
458 }458 }
459- if os.Getenv("ZOT_FORCE_BROWSER") != "" {459+ if os.Getenv("ZUT_FORCE_BROWSER") != "" {
460 return true460 return true
461 }461 }
462 if _, err := os.Stat("/.dockerenv"); err == nil {462 if _, err := os.Stat("/.dockerenv"); err == nil {
M packages/provider/auth/oauth.go +5 -3
@@ -20,7 +20,7 @@ import (
20 // WARNING: the client ids used here belong to the official first-party20 // WARNING: the client ids used here belong to the official first-party
21 // CLIs (Claude Code for Anthropic, Codex CLI for OpenAI). Using them from21 // CLIs (Claude Code for Anthropic, Codex CLI for OpenAI). Using them from
22 // third-party software is against the respective terms of service and22 // third-party software is against the respective terms of service and
23-// may be revoked at any time. zot exposes this only behind23+// may be revoked at any time. zut exposes this only behind
24 // --experimental-oauth.24 // --experimental-oauth.
25 type OAuthProvider struct {25 type OAuthProvider struct {
26 Name string26 Name string
@@ -78,7 +78,7 @@ var (
78 78
79 // Anthropic manual / headless variant: redirects to Anthropic's79 // Anthropic manual / headless variant: redirects to Anthropic's
80 // copy-code page instead of a local loopback port, used when there80 // 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 Docker81+ // is no browser on the machine running zut (e.g. inside a Docker
82 // container or over plain SSH).82 // container or over plain SSH).
83 AnthropicManualOAuth = OAuthProvider{83 AnthropicManualOAuth = OAuthProvider{
84 Name: "anthropic",84 Name: "anthropic",
@@ -108,7 +108,9 @@ var (
108 ExtraAuthArgs: map[string]string{108 ExtraAuthArgs: map[string]string{
109 "id_token_add_organizations": "true",109 "id_token_add_organizations": "true",
110 "codex_cli_simplified_flow": "true",110 "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",
112 },114 },
113 }115 }
114 116
M packages/provider/auth/server.go +11 -11
@@ -270,11 +270,11 @@ func Redirect(w http.ResponseWriter, r *http.Request, u *url.URL) {
270 // the browser tab looks like the tui: black on white, monospace, thin270 // the browser tab looks like the tui: black on white, monospace, thin
271 // rules, no rounded boxes, no color.271 // rules, no rounded boxes, no color.
272 272
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>
274 ` + logoTag + `274 ` + logoTag + `
275-<h1><span class="zot">zot</span> login</h1>275+<h1><span class="zut">zut</span> login</h1>
276 <hr class="rule">276 <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>
278 <p>278 <p>
279 <a href="/apikey?provider=anthropic">anthropic api key →</a><br>279 <a href="/apikey?provider=anthropic">anthropic api key →</a><br>
280 <a href="/apikey?provider=openai">openai api key →</a><br>280 <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="
283 <a href="/apikey?provider=google">google gemini api key →</a>283 <a href="/apikey?provider=google">google gemini api key →</a>
284 </p>284 </p>
285 <hr class="rule">285 <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>
287 </body></html>`))287 </body></html>`))
288 288
289 func init() {289 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>
291 form { display: flex; flex-direction: column; gap: 0.75rem; }291 form { display: flex; flex-direction: column; gap: 0.75rem; }
292 label { font-size: 0.875rem; }292 label { font-size: 0.875rem; }
293 </style></head><body>293 </style></head><body>
294 ` + logoTag + `294 ` + 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>
296 <hr class="rule">296 <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>
298 <form method="POST" action="/apikey">298 <form method="POST" action="/apikey">
299 <input type="hidden" name="provider" value="{{.Provider}}" />299 <input type="hidden" name="provider" value="{{.Provider}}" />
300 <label for="api_key">api key</label>300 <label for="api_key">api key</label>
@@ -303,19 +303,19 @@ func init() {
303 </form>303 </form>
304 </body></html>`))304 </body></html>`))
305 305
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>
307 ` + logoTag + `307 ` + logoTag + `
308 <h1><span class="mark">✓</span> logged in to {{.Provider}}</h1>308 <h1><span class="mark">✓</span> logged in to {{.Provider}}</h1>
309 <hr class="rule">309 <hr class="rule">
310 <p class="msg">method: {{.Method}}</p>310 <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>
312 </body></html>`))312 </body></html>`))
313 313
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>
315 ` + logoTag + `315 ` + logoTag + `
316 <h1><span class="mark">✗</span> login failed</h1>316 <h1><span class="mark">✗</span> login failed</h1>
317 <hr class="rule">317 <hr class="rule">
318 <p class="msg mono">{{.Message}}</p>318 <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>
320 </body></html>`))320 </body></html>`))
321 }321 }
M packages/provider/auth/store.go +2 -2
@@ -1,7 +1,7 @@
1 // Package auth handles credential storage and the login methods1 // 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.
3 //3 //
4-// All credentials live in $ZOT_HOME/auth.json (mode 0600).4+// All credentials live in $ZUT_HOME/auth.json (mode 0600).
5 package auth5 package auth
6 6
7 import (7 import (
M packages/provider/catalog_builtin.go +1 -1
@@ -6,7 +6,7 @@ package provider
6 //6 //
7 // This file is data; the values are tracked verbatim from each7 // This file is data; the values are tracked verbatim from each
8 // vendor's own public pricing / model-list pages. They drift; users8 // 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).
10 //10 //
11 // Curated entries in models.go (anthropic / openai / openai-codex /11 // Curated entries in models.go (anthropic / openai / openai-codex /
12 // the small seed list for kimi / deepseek / google) are not duplicated12 // 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
3 // Hand-curated catalog supplements that the bulk builtin catalog3 // Hand-curated catalog supplements that the bulk builtin catalog
4 // (catalog_builtin.go) doesn't cover.4 // (catalog_builtin.go) doesn't cover.
5 //5 //
6-// - openai-responses: zot-specific provider id for the public OpenAI6+// - openai-responses: zut-specific provider id for the public OpenAI
7 // Responses API (api.openai.com/v1/responses). Separate from7 // Responses API (api.openai.com/v1/responses). Separate from
8 // `openai` (Chat Completions) and `openai-codex` (ChatGPT subscription).8 // `openai` (Chat Completions) and `openai-codex` (ChatGPT subscription).
9 // - kimi/kimi-k2-thinking: matches the Anthropic-Messages endpoint9 // - 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 {
326 326
327 // NewGithubCopilot returns a GitHub Copilot client. The provided327 // NewGithubCopilot returns a GitHub Copilot client. The provided
328 // credential must be a GitHub Personal Access Token (PAT) with Copilot328 // credential must be a GitHub Personal Access Token (PAT) with Copilot
329-// access enabled; zot trades it for a short-lived Copilot token on329+// access enabled; zut trades it for a short-lived Copilot token on
330 // every inference request (cached in memory until ~5min before expiry).330 // every inference request (cached in memory until ~5min before expiry).
331 //331 //
332 // Wire format: OpenAI Chat Completions. Copilot-specific headers332 // Wire format: OpenAI Chat Completions. Copilot-specific headers
M packages/provider/gemini.go +4 -4
@@ -25,7 +25,7 @@ import (
25 // consumer Gemini Advanced / Google One AI subscriptions; programmatic25 // consumer Gemini Advanced / Google One AI subscriptions; programmatic
26 // access requires either an AI Studio API key (this client) or Vertex26 // access requires either an AI Studio API key (this client) or Vertex
27 // AI / GCP service-account credentials (separate provider, not yet27 // AI / GCP service-account credentials (separate provider, not yet
28-// implemented in zot).28+// implemented in zut).
29 29
30 const geminiDefaultBaseURL = "https://generativelanguage.googleapis.com"30 const geminiDefaultBaseURL = "https://generativelanguage.googleapis.com"
31 31
@@ -54,7 +54,7 @@ func (c *geminiClient) Name() string { return "google" }
54 // ---- wire types ----54 // ---- wire types ----
55 //55 //
56 // Subset of Gemini's Content / Part / GenerateContentRequest schema.56 // 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.
58 58
59 type gemInlineData struct {59 type gemInlineData struct {
60 MimeType string `json:"mimeType"`60 MimeType string `json:"mimeType"`
@@ -352,7 +352,7 @@ func saveGeminiImageToWorkingDir(mimeType string, data []byte) (string, error) {
352 case "image/gif":352 case "image/gif":
353 ext = ".gif"353 ext = ".gif"
354 }354 }
355- name := "zot-gemini-image-" + uuid.NewString() + ext355+ name := "zut-gemini-image-" + uuid.NewString() + ext
356 path := filepath.Join(".", name)356 path := filepath.Join(".", name)
357 if err := os.WriteFile(path, data, 0o644); err != nil {357 if err := os.WriteFile(path, data, 0o644); err != nil {
358 return "", err358 return "", err
@@ -406,7 +406,7 @@ func convertGemToolResultParts(blocks []Content) []gemPart {
406 return parts406 return parts
407 }407 }
408 408
409-// geminiThinkingConfig maps zot's reasoning level ("low"/"medium"/"high")409+// geminiThinkingConfig maps zut's reasoning level ("low"/"medium"/"high")
410 // to Gemini's thinkingConfig. The right knob depends on the model410 // to Gemini's thinkingConfig. The right knob depends on the model
411 // generation: 2.5 family uses thinkingBudget (tokens), 3.x uses411 // generation: 2.5 family uses thinkingBudget (tokens), 3.x uses
412 // thinkingLevel (enum). Returns nil when the level is unrecognised.412 // 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) {
111 t.Fatalf("image=%q %q", ib.MimeType, string(ib.Data))111 t.Fatalf("image=%q %q", ib.MimeType, string(ib.Data))
112 }112 }
113 saved, ok := done.Message.Content[2].(TextBlock)113 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") {
115 t.Fatalf("saved path block=%T %+v", done.Message.Content[2], done.Message.Content[2])115 t.Fatalf("saved path block=%T %+v", done.Message.Content[2], done.Message.Content[2])
116 }116 }
117 path := strings.TrimPrefix(saved.Text, "Saved image: `")117 path := strings.TrimPrefix(saved.Text, "Saved image: `")
@@ -184,7 +184,7 @@ func TestGeminiBuildRequestSystemAndTools(t *testing.T) {
184 c := NewGemini("k", "https://example.invalid").(*geminiClient)184 c := NewGemini("k", "https://example.invalid").(*geminiClient)
185 wire, _, err := c.buildRequest(Request{185 wire, _, err := c.buildRequest(Request{
186 Model: "gemini-2.5-pro",186 Model: "gemini-2.5-pro",
187- System: "you are zot",187+ System: "you are zut",
188 Tools: []Tool{188 Tools: []Tool{
189 {Name: "read", Description: "read a file", Schema: json.RawMessage(`{"type":"object","properties":{"path":{"type":"string"}}}`)},189 {Name: "read", Description: "read a file", Schema: json.RawMessage(`{"type":"object","properties":{"path":{"type":"string"}}}`)},
190 },190 },
@@ -193,7 +193,7 @@ func TestGeminiBuildRequestSystemAndTools(t *testing.T) {
193 if err != nil {193 if err != nil {
194 t.Fatal(err)194 t.Fatal(err)
195 }195 }
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" {
197 t.Fatalf("system: %+v", wire.SystemInstruction)197 t.Fatalf("system: %+v", wire.SystemInstruction)
198 }198 }
199 if len(wire.Tools) != 1 || len(wire.Tools[0].FunctionDeclarations) != 1 || wire.Tools[0].FunctionDeclarations[0].Name != "read" {199 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) {
537 }537 }
538 }538 }
539 539
540-// TestGeminiBuildRequestWithReasoningReplay verifies that when zot sends a540+// TestGeminiBuildRequestWithReasoningReplay verifies that when zut sends a
541 // historical assistant message containing a ReasoningBlock, buildRequest541 // historical assistant message containing a ReasoningBlock, buildRequest
542 // serializes it into the verbatim Gemini "thought: true" wire representation542 // serializes it into the verbatim Gemini "thought: true" wire representation
543 // with its corresponding thoughtSignature.543 // with its corresponding thoughtSignature.
M packages/provider/github_copilot.go +2 -2
@@ -5,7 +5,7 @@ package provider
5 // Auth chain:5 // Auth chain:
6 //6 //
7 // 1. User stores a GitHub Personal Access Token (PAT) in7 // 1. User stores a GitHub Personal Access Token (PAT) in
8-// COPILOT_GITHUB_TOKEN (or via zot's auth.json, when wired). The PAT8+// COPILOT_GITHUB_TOKEN (or via zut's auth.json, when wired). The PAT
9 // is the long-lived credential; it never hits the inference endpoint.9 // is the long-lived credential; it never hits the inference endpoint.
10 // 2. We trade the PAT for a short-lived Copilot token by hitting10 // 2. We trade the PAT for a short-lived Copilot token by hitting
11 // `GET https://api.github.com/copilot_internal/v2/token` with the PAT11 // `GET https://api.github.com/copilot_internal/v2/token` with the PAT
@@ -19,7 +19,7 @@ package provider
19 // - X-Initiator: user|agent19 // - X-Initiator: user|agent
20 // - Openai-Intent: conversation-edits20 // - Openai-Intent: conversation-edits
21 // - Copilot-Vision-Request: true (when images present; not wired21 // - 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)
23 //23 //
24 // Token caching: short-lived tokens last ~30min. We cache one per PAT in24 // Token caching: short-lived tokens last ~30min. We cache one per PAT in
25 // memory for the process lifetime and refresh on demand. No disk cache.25 // 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 (
52 "sync"52 "sync"
53 "time"53 "time"
54 54
55- providerauth "github.com/patriceckhart/zot/packages/provider/auth"55+ providerauth "github.com/bnema/zut/packages/provider/auth"
56 )56 )
57 57
58 type vertexConfig struct {58 type vertexConfig struct {
M packages/provider/llamacpp.go +1 -1
@@ -419,7 +419,7 @@ func (c *LlamaCPPClient) DownloadAndWait(ctx context.Context, model string, upda
419 }419 }
420 }420 }
421 421
422-// LlamaCPPModels converts loaded router entries into zot model metadata.422+// LlamaCPPModels converts loaded router entries into zut model metadata.
423 func LlamaCPPModels(models []LlamaCPPModel, serverURL string) []Model {423 func LlamaCPPModels(models []LlamaCPPModel, serverURL string) []Model {
424 baseURL, err := LlamaCPPInferenceURL(serverURL)424 baseURL, err := LlamaCPPInferenceURL(serverURL)
425 if err != nil {425 if err != nil {
M packages/provider/models.go +1 -1
@@ -60,7 +60,7 @@ type Model struct {
60 }60 }
61 61
62 // Catalog is the hardcoded, read-only list of supported models.62 // Catalog is the hardcoded, read-only list of supported models.
63-// Prices are USD per 1M tokens. The list is curated to what zot's63+// Prices are USD per 1M tokens. The list is curated to what zut's
64 // clients (Anthropic Messages + OpenAI Chat Completions) can actually64 // clients (Anthropic Messages + OpenAI Chat Completions) can actually
65 // talk to; models that are only reachable through the OpenAI Responses65 // talk to; models that are only reachable through the OpenAI Responses
66 // API (o1-pro, o3-pro, gpt-5-pro) are omitted.66 // 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) {
261 // Some gateways expose adaptive-thinking Anthropic models through261 // Some gateways expose adaptive-thinking Anthropic models through
262 // the OpenAI-compatible chat-completions wire. They accept the262 // the OpenAI-compatible chat-completions wire. They accept the
263 // same reasoning_effort knob, including the top "xhigh" tier;263 // 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.
265 effort = OpenAICompatAnthropicEffort(reasoning)265 effort = OpenAICompatAnthropicEffort(reasoning)
266 }266 }
267 if effort != "" {267 if effort != "" {
@@ -341,7 +341,7 @@ func (c *openaiClient) buildRequest(req Request) (*oaiRequest, error) {
341 }341 }
342 // Kimi rejects assistant messages with neither visible text nor342 // Kimi rejects assistant messages with neither visible text nor
343 // tool calls ("assistant must not be empty"). This can happen when343 // tool calls ("assistant must not be empty"). This can happen when
344- // a previous stream produced only reasoning_content, which zot keeps344+ // a previous stream produced only reasoning_content, which zut keeps
345 // internally for provider replay but cannot send back as standalone345 // internally for provider replay but cannot send back as standalone
346 // assistant content on OpenAI-compatible chat-completions APIs.346 // assistant content on OpenAI-compatible chat-completions APIs.
347 if am.Content == nil && len(am.ToolCalls) == 0 {347 if am.Content == nil && len(am.ToolCalls) == 0 {
M packages/provider/openai_codex.go +5 -3
@@ -21,7 +21,7 @@ import (
21 // Wire protocol notes:21 // Wire protocol notes:
22 // - Endpoint: POST https://chatgpt.com/backend-api/codex/responses22 // - Endpoint: POST https://chatgpt.com/backend-api/codex/responses
23 // - Headers: Authorization: Bearer <access_token>, chatgpt-account-id: <id>,23 // - Headers: Authorization: Bearer <access_token>, chatgpt-account-id: <id>,
24-// OpenAI-Beta: responses=experimental, originator: zot24+// OpenAI-Beta: responses=experimental, originator: zot (required legacy value)
25 // - Body: OpenAI Responses API shape (not chat/completions).25 // - Body: OpenAI Responses API shape (not chat/completions).
26 // input: [{role, content: [{type: "input_text" | "input_image" | ... }]}]26 // input: [{role, content: [{type: "input_text" | "input_image" | ... }]}]
27 // instructions: <system prompt>27 // instructions: <system prompt>
@@ -75,7 +75,7 @@ func NewOpenAICodex(token, accountID, baseURL string) Client {
75 75
76 func (c *codexClient) Name() string { return "openai-codex" }76 func (c *codexClient) Name() string { return "openai-codex" }
77 77
78-// ---- Responses API wire types (subset needed for zot's surface) ----78+// ---- Responses API wire types (subset needed for zut's surface) ----
79 79
80 type codexInputText struct {80 type codexInputText struct {
81 Type string `json:"type"` // "input_text"81 Type string `json:"type"` // "input_text"
@@ -360,7 +360,7 @@ func usesCodexCLIRouting(model string) bool {
360 func newCodexSessionID() string {360 func newCodexSessionID() string {
361 var b [16]byte361 var b [16]byte
362 if _, err := rand.Read(b[:]); err != nil {362 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())
364 }364 }
365 return strings.Join([]string{365 return strings.Join([]string{
366 hex.EncodeToString(b[0:4]),366 hex.EncodeToString(b[0:4]),
@@ -410,6 +410,8 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
410 httpReq.Header.Set("session-id", codexCLISessionID)410 httpReq.Header.Set("session-id", codexCLISessionID)
411 httpReq.Header.Set("user-agent", "codex_cli_rs/0.0.0")411 httpReq.Header.Set("user-agent", "codex_cli_rs/0.0.0")
412 } else {412 } else {
413+ // ChatGPT's backend recognizes these established values. They are
414+ // external provider metadata, not zut product identifiers.
413 httpReq.Header.Set("originator", "zot")415 httpReq.Header.Set("originator", "zot")
414 httpReq.Header.Set("user-agent", fmt.Sprintf("zot (%s %s)", runtime.GOOS, runtime.GOARCH))416 httpReq.Header.Set("user-agent", fmt.Sprintf("zot (%s %s)", runtime.GOOS, runtime.GOARCH))
415 }417 }
M packages/provider/openai_codex_test.go +7 -0
@@ -6,6 +6,7 @@ import (
6 "encoding/json"6 "encoding/json"
7 "io"7 "io"
8 "net/http"8 "net/http"
9+ "runtime"
9 "strings"10 "strings"
10 "testing"11 "testing"
11 )12 )
@@ -175,6 +176,12 @@ func TestOpenAIGPT56DoesNotUseCodexCLIRouting(t *testing.T) {
175 if gotBody.PromptCacheKey != "" {176 if gotBody.PromptCacheKey != "" {
176 t.Fatalf("prompt_cache_key = %q", gotBody.PromptCacheKey)177 t.Fatalf("prompt_cache_key = %q", gotBody.PromptCacheKey)
177 }178 }
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+ }
178 }185 }
179 186
180 func TestCodexNestedStreamError(t *testing.T) {187 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.
2 //2 //
3 // It supports exactly two providers: Anthropic (Messages API) and3 // It supports exactly two providers: Anthropic (Messages API) and
4 // OpenAI (Chat Completions API). Everything above this package operates4 // OpenAI (Chat Completions API). Everything above this package operates
@@ -69,7 +69,7 @@ func (ToolResultBlock) isContent() {}
69 // providers that require it on follow-up requests (OpenAI Codex with69 // providers that require it on follow-up requests (OpenAI Codex with
70 // thinking enabled) can replay the same payload they emitted earlier.70 // thinking enabled) can replay the same payload they emitted earlier.
71 // Summary is the human-readable reasoning summary (may be empty); the71 // Summary is the human-readable reasoning summary (may be empty); the
72-// encrypted blob is opaque to zot. ID is the provider-issued reasoning72+// encrypted blob is opaque to zut. ID is the provider-issued reasoning
73 // item id.73 // item id.
74 type ReasoningBlock struct {74 type ReasoningBlock struct {
75 ID string `json:"reasoning_id,omitempty"`75 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 collapsed to keep the page light
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