main 4509ec7

README.md RawHistoryFolder

secret — example zut extension

Demonstrates secret collection via a masked panel: the model asks for a resource that needs a credential, the extension collects the credential directly from the user inside a panel, uses it to perform the operation, and returns only the outcome to the model. The secret is never written to any JSON frame or the transcript.

What it does

Registers one LLM-callable tool:

fetch_with_password(url: string)

When the model calls it, a masked password panel opens:

╭─ Password required ─────────────────────╮
│  URL:       https://internal.example.com │
│                                          │
│  Password:  ●●●●●●●▌                    │
╰──────────────────────────────────────────╯
  type password  enter confirm  esc cancel

The panel re-renders after every keystroke, showing bullets instead of characters. Pressing Enter unblocks the tool goroutine, which uses the password directly (in doFetch) and returns only the fetch outcome to the model.

Security property

The password lives only in the extension process's memory. It is never serialised into a JSON frame, never appears in the transcript, and never reaches the model. The model sees:

fetched https://internal.example.com successfully (password was 9 characters, not shown)

Build

cd examples/extensions/secret
go build -o secret .

Install

zut ext install .

Try it

In zut, ask:

Fetch https://internal.example.com/report — it needs a password.

The model calls fetch_with_password; the masked panel opens. Type anything and press Enter. The model receives the result; the password is gone.

Adapting to real use

Replace doFetch in main.go with a real HTTP request:

func doFetch(url, password string) ext.ToolResult {
    req, _ := http.NewRequest("GET", url, nil)
    req.SetBasicAuth("user", password)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return ext.TextErrorResult("fetch failed: " + err.Error())
    }
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    return ext.TextResult(string(body))
}

The same pattern works for any credential type: API tokens, SSH passphrases, TOTP codes, or freeform override strings.

See also

  • examples/extensions/approve — same pattern for approve/deny gates (no text input, just y/n)
  • docs/extensions.md — full protocol reference
source
1# secret — example zut extension
2
3Demonstrates **secret collection via a masked panel**: the model asks
4for a resource that needs a credential, the extension collects the
5credential directly from the user inside a panel, uses it to perform
6the operation, and returns only the outcome to the model. The secret
7is never written to any JSON frame or the transcript.
8
9## What it does
10
11Registers one LLM-callable tool:
12
13```
14fetch_with_password(url: string)
15```
16
17When the model calls it, a masked password panel opens:
18
19```
20╭─ Password required ─────────────────────╮
21│  URL:       https://internal.example.com │
22│                                          │
23│  Password:  ●●●●●●●▌                    │
24╰──────────────────────────────────────────╯
25  type password  enter confirm  esc cancel
26```
27
28The panel re-renders after every keystroke, showing bullets instead of
29characters. Pressing Enter unblocks the tool goroutine, which uses the
30password directly (in `doFetch`) and returns only the fetch outcome to
31the model.
32
33## Security property
34
35The password lives only in the extension process's memory. It is never
36serialised into a JSON frame, never appears in the transcript, and
37never reaches the model. The model sees:
38
39```
40fetched https://internal.example.com successfully (password was 9 characters, not shown)
41```
42
43## Build
44
45```bash
46cd examples/extensions/secret
47go build -o secret .
48```
49
50## Install
51
52```bash
53zut ext install .
54```
55
56## Try it
57
58In zut, ask:
59
60> Fetch https://internal.example.com/report — it needs a password.
61
62The model calls `fetch_with_password`; the masked panel opens. Type
63anything and press **Enter**. The model receives the result; the
64password is gone.
65
66## Adapting to real use
67
68Replace `doFetch` in `main.go` with a real HTTP request:
69
70```go
71func doFetch(url, password string) ext.ToolResult {
72    req, _ := http.NewRequest("GET", url, nil)
73    req.SetBasicAuth("user", password)
74    resp, err := http.DefaultClient.Do(req)
75    if err != nil {
76        return ext.TextErrorResult("fetch failed: " + err.Error())
77    }
78    defer resp.Body.Close()
79    body, _ := io.ReadAll(resp.Body)
80    return ext.TextResult(string(body))
81}
82```
83
84The same pattern works for any credential type: API tokens, SSH
85passphrases, TOTP codes, or freeform override strings.
86
87## See also
88
89- `examples/extensions/approve` — same pattern for approve/deny gates
90  (no text input, just y/n)
91- `docs/extensions.md` — full protocol reference

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