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