v0.3.40 dad7bb4

args.go RawHistoryFolder

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

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 477

↑↓ moveenter openesc close