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. In -p / --json / rpc
73 // modes there's no interactive TUI, so --no-yolo auto-refuses
74 // every tool call with a reason the model can learn from.
75 NoYolo bool
76
77 ListModels bool
78 Help bool
79 Version bool
80
81 Prompt string // concatenated positional args
82}
83
84// ParseArgs parses the process arguments (excluding argv[0]).
85func ParseArgs(in []string) (Args, error) {
86 a := Args{Mode: ModeInteractive, MaxSteps: 50}
87 positional := []string{}
88
89 want := func(i *int, flag string) (string, error) {
90 *i++
91 if *i >= len(in) {
92 return "", fmt.Errorf("%s requires a value", flag)
93 }
94 return in[*i], nil
95 }
96
97 for i := 0; i < len(in); i++ {
98 arg := in[i]
99 switch arg {
100 case "-h", "--help":
101 a.Help = true
102 case "-v", "--version":
103 a.Version = true
104 case "-p", "--print":
105 a.Mode = ModePrint
106 case "--json":
107 a.Mode = ModeJSON
108 case "--rpc":
109 a.Mode = ModeRPC
110 case "-c", "--continue":
111 a.Continue = true
112 case "-r", "--resume":
113 a.Resume = true
114 case "--no-session":
115 a.NoSess = true
116 case "--no-tools":
117 a.NoTools = true
118 case "--list-models":
119 a.ListModels = true
120 case "--experimental-oauth":
121 // deprecated: subscription login is always available.
122 // accepted silently for backwards compatibility.
123 case "--provider":
124 v, err := want(&i, arg)
125 if err != nil {
126 return a, err
127 }
128 a.Provider = v
129 case "--model":
130 v, err := want(&i, arg)
131 if err != nil {
132 return a, err
133 }
134 a.Model = v
135 case "--api-key":
136 v, err := want(&i, arg)
137 if err != nil {
138 return a, err
139 }
140 a.APIKey = v
141 case "--base-url":
142 v, err := want(&i, arg)
143 if err != nil {
144 return a, err
145 }
146 a.BaseURL = v
147 case "--system-prompt":
148 v, err := want(&i, arg)
149 if err != nil {
150 return a, err
151 }
152 a.SystemPrompt = v
153 case "--append-system-prompt":
154 v, err := want(&i, arg)
155 if err != nil {
156 return a, err
157 }
158 a.AppendSystemPrompt = append(a.AppendSystemPrompt, v)
159 case "--ext", "-e":
160 v, err := want(&i, arg)
161 if err != nil {
162 return a, err
163 }
164 // Repeatable; each value is a directory containing an
165 // extension.json. Resolved to absolute later so paths like
166 // "." survive a later cwd change.
167 a.Exts = append(a.Exts, v)
168 case "--no-ext", "--no-extensions":
169 a.NoExt = true
170 case "--no-skill", "--no-skills":
171 a.NoSkill = true
172 case "--with-skills", "--with-skill":
173 a.WithSkills = true
174 case "--no-yolo":
175 a.NoYolo = true
176 case "--reasoning":
177 v, err := want(&i, arg)
178 if err != nil {
179 return a, err
180 }
181 switch strings.ToLower(v) {
182 case "", "low", "medium", "high":
183 a.Reasoning = strings.ToLower(v)
184 default:
185 return a, fmt.Errorf("--reasoning must be low|medium|high")
186 }
187 case "--session":
188 v, err := want(&i, arg)
189 if err != nil {
190 return a, err
191 }
192 a.Session = v
193 case "--cwd":
194 v, err := want(&i, arg)
195 if err != nil {
196 return a, err
197 }
198 a.CWD = v
199 case "--tools":
200 v, err := want(&i, arg)
201 if err != nil {
202 return a, err
203 }
204 for _, t := range strings.Split(v, ",") {
205 t = strings.TrimSpace(t)
206 if t != "" {
207 a.Tools = append(a.Tools, t)
208 }
209 }
210 case "--max-steps":
211 v, err := want(&i, arg)
212 if err != nil {
213 return a, err
214 }
215 var n int
216 if _, err := fmt.Sscanf(v, "%d", &n); err != nil || n <= 0 {
217 return a, fmt.Errorf("--max-steps must be a positive integer")
218 }
219 a.MaxSteps = n
220 default:
221 if strings.HasPrefix(arg, "-") && arg != "-" {
222 return a, fmt.Errorf("unknown flag %q", arg)
223 }
224 positional = append(positional, arg)
225 }
226 }
227
228 if len(positional) > 0 {
229 a.Prompt = strings.Join(positional, " ")
230 }
231
232 if a.CWD == "" {
233 a.CWD, _ = os.Getwd()
234 }
235 return a, nil
236}
237
238// PrintHelp writes the help text to w (stderr by default).
239func PrintHelp(version string) {
240 fmt.Fprintf(os.Stderr, `zot %s — Yet another coding agent harness.
241
242usage:
243 zot interactive tui
244 zot "prompt" interactive, pre-filled prompt
245 zot -p "prompt" print final text, exit
246 zot --json "prompt" newline-delimited json events, exit
247 zot rpc json-rpc loop on stdin/stdout (see docs/rpc.md)
248 zot telegram-bot setup configure a telegram bot (from BotFather)
249 zot telegram-bot run foreground bridge (ctrl+c to stop)
250 zot telegram-bot start background bridge (detached)
251 zot telegram-bot stop stop the background bridge
252 zot telegram-bot logs [-f] tail the background bridge's log
253 zot telegram-bot status config + running state
254 zot telegram-bot reset forget saved token
255 (short alias: zot tg ...)
256
257flags:
258 --provider anthropic|openai
259 --model model id (see --list-models)
260 --api-key api key for this run (falls back to env and auth.json)
261 --base-url override provider api base url (for testing/self-hosted)
262
263 --system-prompt TEXT replace the default system prompt
264 --append-system-prompt TEXT append to the system prompt (repeatable)
265 --reasoning low|medium|high enable reasoning on supported models
266
267 -c, --continue continue the most recent session for this cwd
268 -r, --resume pick a session to resume
269 --session PATH resume a specific session file
270 --no-session do not read or write a session file
271
272 --cwd PATH treat PATH as the working directory
273 --no-tools disable all tools
274 --tools csv only enable the listed tools
275 -e, --ext PATH load an extension from PATH for this run
276 (repeatable; takes precedence over
277 installed extensions of the same name)
278 --no-ext skip extension discovery for this run
279 (--ext PATH still works on top, so
280 --no-ext --ext ./x runs only x)
281 --no-skill skip skill discovery for this run,
282 including built-in skills (no skill
283 tool, no Available skills manifest)
284 --with-skills load user-installed skills from
285 $ZOT_HOME/skills/ + .zot/skills/ +
286 .claude/skills/ + .agents/skills/.
287 default: only built-in skills load
288
289 --no-yolo ask before running every tool call
290 (interactive mode only; in -p / --json
291 / rpc this refuses every tool call)
292
293 --max-steps N agent loop iteration cap (default 50)
294 --list-models print known models and exit
295 -h, --help this message
296 -v, --version version info
297`, version)
298}