v0.3.29 f3d8eb6

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

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