v0.2.40 0250ce1

args.go RawHistoryFolder

1package agent
2
3import (
4	"fmt"
5	"os"
6	"strconv"
7	"strings"
8
9	"github.com/patriceckhart/zot/packages/tui"
10	"golang.org/x/term"
11)
12
13// Mode is the CLI run mode.
14type Mode string
15
16const (
17	ModeInteractive Mode = "interactive"
18	ModePrint       Mode = "print"
19	ModeJSON        Mode = "json"
20	ModeRPC         Mode = "rpc"
21	// ModeSwarmAgent is the long-lived, headless daemon mode used by
22	// swarm-spawned agents. The binary opens a unix-socket inbox at
23	// the path provided by --swarm-agent, reads supervisor messages
24	// off it ("user ...", "cancel", "shutdown"), runs each user turn
25	// against a persistent session, and streams JSONL events on
26	// stdout. See packages/agent/swarm/inbox.go for the wire protocol.
27	ModeSwarmAgent Mode = "swarm-agent"
28)
29
30// Args holds parsed command-line options.
31type Args struct {
32	Mode     Mode
33	Provider string
34	Model    string
35	APIKey   string
36
37	BaseURL            string // override provider base URL (for tests/self-hosted)
38	SystemPrompt       string
39	AppendSystemPrompt []string
40	Reasoning          string
41	Temperature        *float32
42
43	Continue bool
44	Resume   bool
45	Session  string
46	NoSess   bool
47
48	CWD      string
49	NoTools  bool
50	Tools    []string
51	MaxSteps int
52
53	// Exts is a list of directory paths the user passed via --ext.
54	// Each must contain an extension.json. Loaded for one session
55	// only; never persisted. Take precedence over installed exts of
56	// the same name.
57	Exts []string
58
59	// NoExt disables extension discovery + spawn entirely for this
60	// run. --ext PATH still works (explicit beats implicit) so you
61	// can run "with only this one extension" via --no-ext --ext PATH.
62	NoExt bool
63
64	// NoSkill disables ALL skill discovery for this run, including
65	// the built-in skills compiled into the binary. The system
66	// prompt loses its "Available skills" manifest and the `skill`
67	// tool isn't registered. Useful for running zot without any
68	// extra context biasing the model.
69	NoSkill bool
70
71	// WithSkills controls loading user-installed skills from
72	// $ZOT_HOME/skills/, .zot/skills/, .claude/skills/, and
73	// .agents/skills/. It defaults to true; --no-skill disables all
74	// skill discovery, including built-ins.
75	WithSkills bool
76
77	// InsecureTLS skips TLS verification for custom inference endpoints.
78	InsecureTLS bool
79
80	// NoYolo turns on per-tool confirmation. Before each tool
81	// invocation the TUI prompts the user with the tool name + args
82	// and waits for an explicit yes/no. The user can also pick
83	// "always for this tool this session" or "always for anything
84	// this session" to stop being prompted again. Defaults off
85	// (yolo mode): tools run without asking.
86	//
87	// No effect in -p / --json / rpc modes, which have no
88	// interactive prompt. A warning is printed to stderr on startup
89	// so scripts know the flag is ignored, but tools still run
90	// freely so automated workflows keep working.
91	NoYolo bool
92
93	ListModels bool
94	Help       bool
95	Version    bool
96
97	Prompt string // concatenated positional args
98
99	// SwarmAgent is the inbox-socket path when this process is a
100	// swarm-spawned agent. Empty in every other mode. Set by
101	// --swarm-agent <path>; presence flips Mode to ModeSwarmAgent.
102	SwarmAgent string
103}
104
105// ParseArgs parses the process arguments (excluding argv[0]).
106func ParseArgs(in []string) (Args, error) {
107	a := Args{Mode: ModeInteractive, MaxSteps: 0, WithSkills: true}
108	positional := []string{}
109
110	want := func(i *int, flag string) (string, error) {
111		*i++
112		if *i >= len(in) {
113			return "", fmt.Errorf("%s requires a value", flag)
114		}
115		return in[*i], nil
116	}
117
118	for i := 0; i < len(in); i++ {
119		arg := in[i]
120		switch arg {
121		case "-h", "--help":
122			a.Help = true
123		case "-v", "--version":
124			a.Version = true
125		case "-p", "--print":
126			a.Mode = ModePrint
127		case "--json":
128			a.Mode = ModeJSON
129		case "--rpc":
130			a.Mode = ModeRPC
131		case "-c", "--continue":
132			a.Continue = true
133		case "-r", "--resume":
134			a.Resume = true
135		case "--no-session":
136			a.NoSess = true
137		case "--no-tools":
138			a.NoTools = true
139		case "--list-models":
140			a.ListModels = true
141		case "--experimental-oauth":
142			// deprecated: subscription login is always available.
143			// accepted silently for backwards compatibility.
144		case "--provider":
145			v, err := want(&i, arg)
146			if err != nil {
147				return a, err
148			}
149			a.Provider = v
150		case "--model":
151			v, err := want(&i, arg)
152			if err != nil {
153				return a, err
154			}
155			a.Model = v
156		case "--api-key":
157			v, err := want(&i, arg)
158			if err != nil {
159				return a, err
160			}
161			a.APIKey = v
162		case "--base-url":
163			v, err := want(&i, arg)
164			if err != nil {
165				return a, err
166			}
167			a.BaseURL = v
168		case "--system-prompt":
169			v, err := want(&i, arg)
170			if err != nil {
171				return a, err
172			}
173			a.SystemPrompt = v
174		case "--append-system-prompt":
175			v, err := want(&i, arg)
176			if err != nil {
177				return a, err
178			}
179			a.AppendSystemPrompt = append(a.AppendSystemPrompt, v)
180		case "--ext", "-e":
181			v, err := want(&i, arg)
182			if err != nil {
183				return a, err
184			}
185			// Repeatable; each value is a directory containing an
186			// extension.json. Resolved to absolute later so paths like
187			// "." survive a later cwd change.
188			a.Exts = append(a.Exts, v)
189		case "--no-ext", "--no-extensions":
190			a.NoExt = true
191		case "--no-skill", "--no-skills":
192			a.NoSkill = true
193		case "--with-skills", "--with-skill":
194			// Deprecated no-op: user skills are loaded by default.
195			a.WithSkills = true
196		case "--insecure":
197			a.InsecureTLS = true
198		case "--no-yolo":
199			a.NoYolo = true
200		case "--reasoning":
201			v, err := want(&i, arg)
202			if err != nil {
203				return a, err
204			}
205			switch strings.ToLower(v) {
206			case "", "off", "minimum", "minimal", "low", "medium", "high", "maximum", "max":
207				a.Reasoning = strings.ToLower(v)
208			default:
209				return a, fmt.Errorf("--reasoning must be off|minimum|low|medium|high|maximum")
210			}
211		case "--temperature":
212			v, err := want(&i, arg)
213			if err != nil {
214				return a, err
215			}
216			f, err := strconv.ParseFloat(v, 32)
217			if err != nil || f < 0 || f > 2 {
218				return a, fmt.Errorf("--temperature must be a number between 0 and 2")
219			}
220			t := float32(f)
221			a.Temperature = &t
222		case "--session":
223			v, err := want(&i, arg)
224			if err != nil {
225				return a, err
226			}
227			a.Session = v
228		case "--cwd":
229			v, err := want(&i, arg)
230			if err != nil {
231				return a, err
232			}
233			a.CWD = v
234		case "--swarm-agent":
235			v, err := want(&i, arg)
236			if err != nil {
237				return a, err
238			}
239			a.SwarmAgent = v
240			a.Mode = ModeSwarmAgent
241		case "--tools":
242			v, err := want(&i, arg)
243			if err != nil {
244				return a, err
245			}
246			for _, t := range strings.Split(v, ",") {
247				t = strings.TrimSpace(t)
248				if t != "" {
249					a.Tools = append(a.Tools, t)
250				}
251			}
252		case "--max-steps":
253			v, err := want(&i, arg)
254			if err != nil {
255				return a, err
256			}
257			var n int
258			if _, err := fmt.Sscanf(v, "%d", &n); err != nil || n <= 0 {
259				return a, fmt.Errorf("--max-steps must be a positive integer")
260			}
261			a.MaxSteps = n
262		default:
263			if strings.HasPrefix(arg, "-") && arg != "-" {
264				return a, fmt.Errorf("unknown flag %q", arg)
265			}
266			positional = append(positional, arg)
267		}
268	}
269
270	if len(positional) > 0 {
271		a.Prompt = strings.Join(positional, " ")
272	}
273
274	if a.CWD == "" {
275		a.CWD, _ = os.Getwd()
276	}
277	return a, nil
278}
279
280// PrintHelp writes the help text to stderr. When stderr is a TTY it
281// uses the same palette as zot's TUI; when redirected it falls back to
282// plain text with no ANSI escapes.
283func PrintHelp(version string) {
284	th := tui.Dark
285	fd := int(os.Stderr.Fd())
286	useColor := term.IsTerminal(fd)
287	style := func(c int, s string) string {
288		if !useColor {
289			return s
290		}
291		return th.FG256(c, s)
292	}
293	assistant := func(s string) string { return style(th.Assistant, s) }
294	muted := func(s string) string { return style(th.Muted, s) }
295	fg := func(s string) string { return style(th.FG, s) }
296	width := 96
297	if useColor {
298		if w, _, err := term.GetSize(fd); err == nil && w > 20 {
299			width = w
300		}
301	}
302	ruleWidth := width
303	if ruleWidth < 40 {
304		ruleWidth = 40
305	}
306	rule := strings.Repeat("─", ruleWidth)
307	if useColor {
308		rule = muted(rule)
309	}
310	leftW := 34
311	if width >= 120 {
312		leftW = 40
313	}
314	if width >= 140 {
315		leftW = 46
316	}
317	type row struct{ left, right string }
318	section := func(title string, rows ...row) {
319		fmt.Fprintln(os.Stderr)
320		fmt.Fprintln(os.Stderr, assistant(title))
321		fmt.Fprintln(os.Stderr, rule)
322		narrow := width < 100
323		for _, r := range rows {
324			if narrow {
325				fmt.Fprintf(os.Stderr, "  %s\n", fg(r.left))
326				fmt.Fprintf(os.Stderr, "    %s\n", muted(r.right))
327				fmt.Fprintln(os.Stderr)
328				continue
329			}
330			left := r.left
331			if len([]rune(left)) < leftW {
332				left += strings.Repeat(" ", leftW-len([]rune(left)))
333			}
334			fmt.Fprintf(os.Stderr, "  %s    %s\n", fg(left), muted(r.right))
335		}
336	}
337
338	fmt.Fprintln(os.Stderr)
339	var headline string
340	if useColor {
341		headline = th.AccentBar(th.Assistant) + assistant(tui.Bold("i'm zot. yet another coding agent harness."))
342	} else {
343		headline = "i'm zot. yet another coding agent harness."
344	}
345	fmt.Fprintln(os.Stderr, headline)
346	fmt.Fprintln(os.Stderr, muted("ask anything, or type /help inside the tui to see commands."))
347	fmt.Fprintf(os.Stderr, "%s %s\n", muted("version:"), fg(version))
348
349	section("modes",
350		row{"zot", "interactive tui"},
351		row{"zot \"prompt\"", "interactive, pre-filled prompt"},
352		row{"zot -p \"prompt\"", "print final text, exit"},
353		row{"zot --json \"prompt\"", "newline-delimited json events, exit"},
354		row{"zot rpc", "json-rpc loop on stdin/stdout (see docs/rpc.md)"},
355	)
356	section("extensions",
357		row{"zot ext list", "list installed extensions"},
358		row{"zot ext install <path|url>", "install into $ZOT_HOME/extensions/"},
359		row{"zot --ext ./path/to/ext", "load an extension for this run only"},
360		row{"zot ext help", "show all extension subcommands"},
361	)
362	section("self-update",
363		row{"zot update", "download and install the latest release"},
364		row{"zot update --check", "show whether a new release is available"},
365	)
366	section("telegram",
367		row{"zot telegram-bot setup", "configure a telegram bot (from BotFather)"},
368		row{"zot telegram-bot run", "foreground bridge (ctrl+c to stop)"},
369		row{"zot telegram-bot start", "background bridge (detached)"},
370		row{"zot telegram-bot stop", "stop the background bridge"},
371		row{"zot telegram-bot logs [-f]", "tail the background bridge log"},
372		row{"zot telegram-bot status", "config + running state"},
373		row{"zot telegram-bot reset", "forget saved token"},
374		row{"zot tg ...", "short alias for telegram-bot"},
375	)
376	section("provider and model flags",
377		row{"--provider", "provider to use (anthropic|openai|openai-codex|kimi|deepseek|google|ollama)"},
378		row{"--model ID", "model id (see --list-models)"},
379		row{"--api-key KEY", "api key for this run (env / auth.json fallback)"},
380		row{"--base-url URL", "override provider api base url"},
381		row{"--insecure", "skip TLS certificate verification (for self-signed-cert endpoints)"},
382		row{"--reasoning off|minimum|low|medium|high|maximum", "set thinking level on supported models"},
383		row{"--temperature N", "sampling temperature, 0 to 2 (omit for provider default)"},
384	)
385	section("prompt and session flags",
386		row{"--system-prompt TEXT", "replace the default system prompt"},
387		row{"--append-system-prompt TEXT", "append to the system prompt (repeatable)"},
388		row{"-c, --continue", "continue the most recent session for this cwd"},
389		row{"-r, --resume", "pick a session to resume"},
390		row{"--session PATH", "resume a specific session file"},
391		row{"--no-session", "do not read or write a session file"},
392	)
393	section("workspace, tools, skills",
394		row{"--cwd PATH", "treat PATH as the working directory"},
395		row{"--no-tools", "disable all tools"},
396		row{"--tools csv", "only enable the listed tools"},
397		row{"--no-yolo", "ask before running every tool call"},
398		row{"--no-ext", "skip extension discovery for this run"},
399		row{"--no-skill", "skip all skill discovery for this run"},
400	)
401	section("misc",
402		row{"--max-steps N", "agent loop iteration cap (default: unlimited)"},
403		row{"--list-models", "print known models and exit"},
404		row{"-h, --help", "show this help"},
405		row{"-v, --version", "show version info"},
406	)
407	fmt.Fprintln(os.Stderr)
408	fmt.Fprintln(os.Stderr, assistant("see also: docs/extensions.md, docs/rpc.md, docs/skills.md"))
409	fmt.Fprintln(os.Stderr)
410}

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