v0.3.34 4548953

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

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