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