1package agent
2
3import (
4 "fmt"
5 "os"
6 "strings"
7)
8
9// Mode is the CLI run mode.
10type Mode string
11
12const (
13 ModeInteractive Mode = "interactive"
14 ModePrint Mode = "print"
15 ModeJSON Mode = "json"
16 ModeRPC Mode = "rpc"
17)
18
19// Args holds parsed command-line options.
20type Args struct {
21 Mode Mode
22 Provider string
23 Model string
24 APIKey string
25
26 BaseURL string // override provider base URL (for tests/self-hosted)
27 SystemPrompt string
28 AppendSystemPrompt []string
29 Reasoning string
30
31 Continue bool
32 Resume bool
33 Session string
34 NoSess bool
35
36 CWD string
37 NoTools bool
38 Tools []string
39 MaxSteps int
40
41 // Exts is a list of directory paths the user passed via --ext.
42 // Each must contain an extension.json. Loaded for one session
43 // only; never persisted. Take precedence over installed exts of
44 // the same name.
45 Exts []string
46
47 // NoExt disables extension discovery + spawn entirely for this
48 // run. --ext PATH still works (explicit beats implicit) so you
49 // can run "with only this one extension" via --no-ext --ext PATH.
50 NoExt bool
51
52 // NoSkill disables ALL skill discovery for this run, including
53 // the built-in skills compiled into the binary. The system
54 // prompt loses its "Available skills" manifest and the `skill`
55 // tool isn't registered. Useful for running zot without any
56 // extra context biasing the model.
57 NoSkill bool
58
59 // WithSkills opts into loading user-installed skills from
60 // $ZOT_HOME/skills/, .zot/skills/, .claude/skills/, and
61 // .agents/skills/. Without this flag only the built-in skills
62 // shipped with the zot binary are available, so a fresh install
63 // has a deterministic skill set regardless of what's lying
64 // around in the user's home directory.
65 WithSkills bool
66
67 // NoYolo turns on per-tool confirmation. Before each tool
68 // invocation the TUI prompts the user with the tool name + args
69 // and waits for an explicit yes/no. The user can also pick
70 // "always for this tool this session" or "always for anything
71 // this session" to stop being prompted again. Defaults off
72 // (yolo mode): tools run without asking.
73 //
74 // No effect in -p / --json / rpc modes, which have no
75 // interactive prompt. A warning is printed to stderr on startup
76 // so scripts know the flag is ignored, but tools still run
77 // freely so automated workflows keep working.
78 NoYolo bool
79
80 ListModels bool
81 Help bool
82 Version bool
83
84 Prompt string // concatenated positional args
85}
86
87// ParseArgs parses the process arguments (excluding argv[0]).
88func ParseArgs(in []string) (Args, error) {
89 a := Args{Mode: ModeInteractive, MaxSteps: 50}
90 positional := []string{}
91
92 want := func(i *int, flag string) (string, error) {
93 *i++
94 if *i >= len(in) {
95 return "", fmt.Errorf("%s requires a value", flag)
96 }
97 return in[*i], nil
98 }
99
100 for i := 0; i < len(in); i++ {
101 arg := in[i]
102 switch arg {
103 case "-h", "--help":
104 a.Help = true
105 case "-v", "--version":
106 a.Version = true
107 case "-p", "--print":
108 a.Mode = ModePrint
109 case "--json":
110 a.Mode = ModeJSON
111 case "--rpc":
112 a.Mode = ModeRPC
113 case "-c", "--continue":
114 a.Continue = true
115 case "-r", "--resume":
116 a.Resume = true
117 case "--no-session":
118 a.NoSess = true
119 case "--no-tools":
120 a.NoTools = true
121 case "--list-models":
122 a.ListModels = true
123 case "--experimental-oauth":
124 // deprecated: subscription login is always available.
125 // accepted silently for backwards compatibility.
126 case "--provider":
127 v, err := want(&i, arg)
128 if err != nil {
129 return a, err
130 }
131 a.Provider = v
132 case "--model":
133 v, err := want(&i, arg)
134 if err != nil {
135 return a, err
136 }
137 a.Model = v
138 case "--api-key":
139 v, err := want(&i, arg)
140 if err != nil {
141 return a, err
142 }
143 a.APIKey = v
144 case "--base-url":
145 v, err := want(&i, arg)
146 if err != nil {
147 return a, err
148 }
149 a.BaseURL = v
150 case "--system-prompt":
151 v, err := want(&i, arg)
152 if err != nil {
153 return a, err
154 }
155 a.SystemPrompt = v
156 case "--append-system-prompt":
157 v, err := want(&i, arg)
158 if err != nil {
159 return a, err
160 }
161 a.AppendSystemPrompt = append(a.AppendSystemPrompt, v)
162 case "--ext", "-e":
163 v, err := want(&i, arg)
164 if err != nil {
165 return a, err
166 }
167 // Repeatable; each value is a directory containing an
168 // extension.json. Resolved to absolute later so paths like
169 // "." survive a later cwd change.
170 a.Exts = append(a.Exts, v)
171 case "--no-ext", "--no-extensions":
172 a.NoExt = true
173 case "--no-skill", "--no-skills":
174 a.NoSkill = true
175 case "--with-skills", "--with-skill":
176 a.WithSkills = true
177 case "--no-yolo":
178 a.NoYolo = true
179 case "--reasoning":
180 v, err := want(&i, arg)
181 if err != nil {
182 return a, err
183 }
184 switch strings.ToLower(v) {
185 case "", "low", "medium", "high":
186 a.Reasoning = strings.ToLower(v)
187 default:
188 return a, fmt.Errorf("--reasoning must be low|medium|high")
189 }
190 case "--session":
191 v, err := want(&i, arg)
192 if err != nil {
193 return a, err
194 }
195 a.Session = v
196 case "--cwd":
197 v, err := want(&i, arg)
198 if err != nil {
199 return a, err
200 }
201 a.CWD = v
202 case "--tools":
203 v, err := want(&i, arg)
204 if err != nil {
205 return a, err
206 }
207 for _, t := range strings.Split(v, ",") {
208 t = strings.TrimSpace(t)
209 if t != "" {
210 a.Tools = append(a.Tools, t)
211 }
212 }
213 case "--max-steps":
214 v, err := want(&i, arg)
215 if err != nil {
216 return a, err
217 }
218 var n int
219 if _, err := fmt.Sscanf(v, "%d", &n); err != nil || n <= 0 {
220 return a, fmt.Errorf("--max-steps must be a positive integer")
221 }
222 a.MaxSteps = n
223 default:
224 if strings.HasPrefix(arg, "-") && arg != "-" {
225 return a, fmt.Errorf("unknown flag %q", arg)
226 }
227 positional = append(positional, arg)
228 }
229 }
230
231 if len(positional) > 0 {
232 a.Prompt = strings.Join(positional, " ")
233 }
234
235 if a.CWD == "" {
236 a.CWD, _ = os.Getwd()
237 }
238 return a, nil
239}
240
241// PrintHelp writes the help text to w (stderr by default).
242func PrintHelp(version string) {
243 fmt.Fprintf(os.Stderr, `zot %s — Yet another coding agent harness.
244
245usage:
246 zot interactive tui
247 zot "prompt" interactive, pre-filled prompt
248 zot -p "prompt" print final text, exit
249 zot --json "prompt" newline-delimited json events, exit
250 zot rpc json-rpc loop on stdin/stdout (see docs/rpc.md)
251 zot telegram-bot setup configure a telegram bot (from BotFather)
252 zot telegram-bot run foreground bridge (ctrl+c to stop)
253 zot telegram-bot start background bridge (detached)
254 zot telegram-bot stop stop the background bridge
255 zot telegram-bot logs [-f] tail the background bridge's log
256 zot telegram-bot status config + running state
257 zot telegram-bot reset forget saved token
258 (short alias: zot tg ...)
259
260flags:
261 --provider anthropic|openai
262 --model model id (see --list-models)
263 --api-key api key for this run (falls back to env and auth.json)
264 --base-url override provider api base url (for testing/self-hosted)
265
266 --system-prompt TEXT replace the default system prompt
267 --append-system-prompt TEXT append to the system prompt (repeatable)
268 --reasoning low|medium|high enable reasoning on supported models
269
270 -c, --continue continue the most recent session for this cwd
271 -r, --resume pick a session to resume
272 --session PATH resume a specific session file
273 --no-session do not read or write a session file
274
275 --cwd PATH treat PATH as the working directory
276 --no-tools disable all tools
277 --tools csv only enable the listed tools
278 -e, --ext PATH load an extension from PATH for this run
279 (repeatable; takes precedence over
280 installed extensions of the same name)
281 --no-ext skip extension discovery for this run
282 (--ext PATH still works on top, so
283 --no-ext --ext ./x runs only x)
284 --no-skill skip skill discovery for this run,
285 including built-in skills (no skill
286 tool, no Available skills manifest)
287 --with-skills load user-installed skills from
288 $ZOT_HOME/skills/ + .zot/skills/ +
289 .claude/skills/ + .agents/skills/.
290 default: only built-in skills load
291
292 --no-yolo ask before running every tool call
293 (interactive tui only; ignored with
294 a stderr warning in -p / --json / rpc)
295
296 --max-steps N agent loop iteration cap (default 50)
297 --list-models print known models and exit
298 -h, --help this message
299 -v, --version version info
300`, version)
301}