v0.1.21 b2fa0e3

main.go RawHistoryFolder

1// guard — phase 3 example: event subscriptions + tool-call interception.
2//
3// What it does:
4//
5//   - Subscribes to turn_start, tool_call, and turn_end events and
6//     writes a one-line audit entry per event to its own log file.
7//
8//   - Intercepts every tool call. If the tool is `bash` and the
9//     command matches a danger pattern (rm -rf, sudo, dd of=/, etc.)
10//     the call is refused and the model gets a one-line refusal it
11//     can react to. Everything else passes through.
12//
13// Build it:
14//
15//	cd examples/extensions/guard
16//	go build -o guard .
17//
18// Drop it next to its extension.json under $ZOT_HOME/extensions/guard/
19// (or `zot ext install ./guard` from this directory) and the next zot
20// session will load it automatically.
21package main
22
23import (
24	"encoding/json"
25	"fmt"
26	"os"
27	"path/filepath"
28	"regexp"
29	"strings"
30	"time"
31
32	"github.com/patriceckhart/zot/pkg/zotext"
33)
34
35var dangerPatterns = []*regexp.Regexp{
36	regexp.MustCompile(`(?i)\brm\s+-rf\b`),
37	regexp.MustCompile(`(?i)\bsudo\b`),
38	regexp.MustCompile(`(?i)\bdd\s+of=/`),
39	regexp.MustCompile(`(?i)\bmkfs\b`),
40	regexp.MustCompile(`(?i)\b:\s*\(\s*\)\s*\{\s*:\|:`), // fork bomb
41	regexp.MustCompile(`(?i)\bchmod\s+-R\s+777\b`),
42}
43
44func main() {
45	ext := zotext.New("guard", "1.0.0")
46
47	auditPath := filepath.Join(os.TempDir(), "zot-guard-audit.log")
48	auditFile, _ := os.OpenFile(auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
49	if auditFile != nil {
50		defer auditFile.Close()
51	}
52	audit := func(format string, args ...any) {
53		line := fmt.Sprintf("%s "+format+"\n", append([]any{time.Now().Format(time.RFC3339)}, args...)...)
54		if auditFile != nil {
55			auditFile.WriteString(line)
56		}
57		ext.Logf("%s", strings.TrimRight(line, "\n"))
58	}
59
60	// Lifecycle observers.
61	ext.On("session_start", func(ev zotext.Event) { audit("[guard] session_start") })
62	ext.On("turn_start", func(ev zotext.Event) { audit("[guard] turn_start step=%d", ev.Step) })
63	ext.On("tool_call", func(ev zotext.Event) {
64		audit("[guard] tool_call %s args=%s", ev.ToolName, string(ev.ToolArgs))
65	})
66	ext.On("turn_end", func(ev zotext.Event) { audit("[guard] turn_end stop=%s err=%s", ev.Stop, ev.Error) })
67
68	// Tool-call gate.
69	ext.InterceptToolCall(func(toolName string, args json.RawMessage) (bool, string) {
70		if toolName != "bash" {
71			return true, "" // only guard bash
72		}
73		var in struct {
74			Command string `json:"command"`
75		}
76		if err := json.Unmarshal(args, &in); err != nil {
77			return true, ""
78		}
79		for _, pat := range dangerPatterns {
80			if pat.MatchString(in.Command) {
81				audit("[guard] BLOCKED bash: %s (matched %s)", in.Command, pat.String())
82				return false, fmt.Sprintf("guard refused: command matches the danger pattern %q. Ask the user before running this.", pat.String())
83			}
84		}
85		return true, ""
86	})
87
88	if err := ext.Run(); err != nil {
89		ext.Logf("fatal: %v", err)
90	}
91}

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