返回 DeepSeek-Reasonix
shell_completion.go
根目录 / internal / cli / shell_completion.go
1 package cli
2
3 import (
4 "fmt"
5 "os"
6 "strconv"
7 "strings"
8
9 "reasonix/internal/agent"
10 )
11
12 type cliCompletionValueKind uint8
13
14 const (
15 cliCompletionNoValue cliCompletionValueKind = iota
16 cliCompletionStaticValue
17 cliCompletionOptionalValue // next token may be a value or another flag (--resume [QUERY])
18 cliCompletionPathValue // path/file; backend returns empty so shells use default file completion
19 cliCompletionModelValue
20 cliCompletionSessionValue
21 )
22
23 type cliCompletionFlag struct {
24 names []string
25 kind cliCompletionValueKind
26 values []string
27 }
28
29 type cliCompletionSpec struct {
30 name string
31 aliases []string
32 flags []cliCompletionFlag
33 subcommands []cliCompletionSpec
34 }
35
36 func completionFlag(names string, kind cliCompletionValueKind, values ...string) cliCompletionFlag {
37 return cliCompletionFlag{names: strings.Fields(names), kind: kind, values: values}
38 }
39
40 func completionSpec(name string, flags []cliCompletionFlag, subcommands ...cliCompletionSpec) cliCompletionSpec {
41 return cliCompletionSpec{name: name, flags: flags, subcommands: subcommands}
42 }
43
44 func completionSpecWithAliases(name string, aliases []string, flags []cliCompletionFlag, subcommands ...cliCompletionSpec) cliCompletionSpec {
45 return cliCompletionSpec{name: name, aliases: aliases, flags: flags, subcommands: subcommands}
46 }
47
48 func cliCompletionRootSpec() cliCompletionSpec {
49 profile := completionFlag("--profile", cliCompletionStaticValue, "economy", "balanced", "delivery")
50 model := completionFlag("--model", cliCompletionModelValue)
51 resume := completionFlag("--resume -r", cliCompletionOptionalValue) // optional QUERY
52 effort := completionFlag("--effort", cliCompletionStaticValue, "auto", "low", "medium", "high", "max")
53 permissionMode := completionFlag("--permission-mode", cliCompletionStaticValue,
54 "manual", "ask", "auto", "acceptEdits", "dontAsk", "plan", "bypassPermissions")
55 help := completionFlag("--help -h", cliCompletionNoValue)
56
57 interactiveFlags := []cliCompletionFlag{
58 model, profile,
59 completionFlag("--max-steps", cliCompletionStaticValue),
60 completionFlag("--continue -c", cliCompletionNoValue),
61 resume,
62 completionFlag("--copy", cliCompletionNoValue),
63 completionFlag("--dangerously-skip-permissions --yolo", cliCompletionNoValue),
64 completionFlag("--dir", cliCompletionPathValue),
65 effort, permissionMode,
66 completionFlag("--add-dir", cliCompletionPathValue),
67 completionFlag("--allowed-tools --allowedTools", cliCompletionStaticValue),
68 help,
69 }
70 // run/serve --resume require a value; only interactive root --resume [QUERY] is optional.
71 runResume := completionFlag("--resume", cliCompletionSessionValue)
72 runFlags := []cliCompletionFlag{
73 model, profile,
74 completionFlag("--max-steps", cliCompletionStaticValue),
75 completionFlag("--show-thinking", cliCompletionNoValue),
76 completionFlag("--metrics", cliCompletionPathValue),
77 completionFlag("--dir", cliCompletionPathValue),
78 completionFlag("--continue -c", cliCompletionNoValue),
79 runResume,
80 completionFlag("--copy", cliCompletionNoValue),
81 effort, permissionMode,
82 completionFlag("--auto -y", cliCompletionNoValue),
83 completionFlag("--print -p", cliCompletionNoValue),
84 completionFlag("--events-jsonl", cliCompletionNoValue),
85 completionFlag("--output-format", cliCompletionStaticValue, "text", "json", "stream-json"),
86 completionFlag("--add-dir", cliCompletionPathValue),
87 completionFlag("--allowed-tools --allowedTools", cliCompletionStaticValue),
88 completionFlag("--ablate", cliCompletionStaticValue, "none", "all", "evidence", "planner", "subagent", "retrieval", "compaction"),
89 help,
90 }
91
92 root := cliCompletionSpec{name: "reasonix", flags: append([]cliCompletionFlag{
93 model,
94 completionFlag("--max-steps", cliCompletionStaticValue),
95 completionFlag("--print -p", cliCompletionNoValue),
96 completionFlag("--continue -c", cliCompletionNoValue),
97 resume,
98 completionFlag("--copy", cliCompletionNoValue),
99 completionFlag("--dangerously-skip-permissions --yolo", cliCompletionNoValue),
100 permissionMode,
101 effort,
102 completionFlag("--dir", cliCompletionPathValue),
103 completionFlag("--add-dir", cliCompletionPathValue),
104 completionFlag("--allowed-tools --allowedTools", cliCompletionStaticValue),
105 completionFlag("--acp", cliCompletionNoValue),
106 completionFlag("--version -v", cliCompletionNoValue),
107 }, help)}
108
109 root.subcommands = []cliCompletionSpec{
110 completionSpec("run", runFlags),
111 completionSpecWithAliases("chat", []string{"code"}, interactiveFlags),
112 completionSpec("serve", []cliCompletionFlag{
113 model, profile,
114 completionFlag("--max-steps", cliCompletionStaticValue),
115 completionFlag("--addr", cliCompletionStaticValue),
116 // serve loads the path with open/loadResumableSession — file path only,
117 // not branch IDs (SessionValue would complete IDs that fail at runtime).
118 completionFlag("--resume", cliCompletionPathValue),
119 completionFlag("--auth", cliCompletionStaticValue, "none", "token", "password"),
120 completionFlag("--token --password --port-file --token-file --pid-file", cliCompletionStaticValue),
121 completionFlag("--hash-password --behind-proxy", cliCompletionNoValue), help,
122 }),
123 completionSpec("setup", []cliCompletionFlag{completionFlag("--local -l", cliCompletionNoValue), help}),
124 completionSpec("config", []cliCompletionFlag{help},
125 completionSpec("auto-plan", []cliCompletionFlag{completionFlag("--local", cliCompletionNoValue), help}),
126 completionSpec("reasoning-language", []cliCompletionFlag{completionFlag("--local", cliCompletionNoValue), help}),
127 completionSpec("compact-ratio", []cliCompletionFlag{completionFlag("--local", cliCompletionNoValue), help}),
128 completionSpec("currency", []cliCompletionFlag{completionFlag("--local", cliCompletionNoValue), help}),
129 completionSpec("telemetry", []cliCompletionFlag{help}),
130 ),
131 completionSpec("init", []cliCompletionFlag{help}),
132 completionSpec("acp", []cliCompletionFlag{
133 model, profile,
134 completionFlag("--planner", cliCompletionStaticValue, "auto", "off"),
135 completionFlag("--sandbox-network", cliCompletionStaticValue, "auto", "on", "off"),
136 completionFlag("--sandbox-bash", cliCompletionStaticValue, "auto", "enforce"),
137 completionFlag("--workspace-only", cliCompletionNoValue), help,
138 }),
139 completionSpec("mcp", []cliCompletionFlag{help},
140 completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{help}),
141 completionSpec("get", []cliCompletionFlag{help}),
142 completionSpec("add", []cliCompletionFlag{
143 completionFlag("--http --streamable-http --sse --env --header", cliCompletionStaticValue), help,
144 }),
145 completionSpec("enable", []cliCompletionFlag{help}),
146 completionSpec("disable", []cliCompletionFlag{help}),
147 completionSpecWithAliases("retry", []string{"connect"}, []cliCompletionFlag{help}),
148 completionSpec("update", []cliCompletionFlag{help}),
149 completionSpec("import", []cliCompletionFlag{help}),
150 completionSpecWithAliases("browse", []string{"search"}, []cliCompletionFlag{
151 completionFlag("--limit", cliCompletionStaticValue), completionFlag("--json", cliCompletionNoValue), help,
152 }),
153 completionSpec("install", []cliCompletionFlag{completionFlag("--as", cliCompletionStaticValue), help}),
154 completionSpecWithAliases("remove", []string{"rm"}, []cliCompletionFlag{help}),
155 ),
156 completionSpec("remote", []cliCompletionFlag{help},
157 completionSpec("add", []cliCompletionFlag{
158 completionFlag("--identity --jump --workspace --serve-install --passphrase-env --password-env", cliCompletionStaticValue),
159 completionFlag("--use-ssh-config", cliCompletionNoValue), help,
160 }),
161 completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{help}),
162 completionSpecWithAliases("remove", []string{"rm"}, []cliCompletionFlag{help}),
163 completionSpec("import", []cliCompletionFlag{completionFlag("--all", cliCompletionNoValue), help}),
164 completionSpec("test", []cliCompletionFlag{help}),
165 completionSpecWithAliases("connect", []string{"open"}, []cliCompletionFlag{
166 completionFlag("--workspace --local-port", cliCompletionStaticValue),
167 completionFlag("--no-serve --open --forward-only", cliCompletionNoValue), help,
168 }),
169 completionSpec("status", []cliCompletionFlag{help}),
170 completionSpec("forward", []cliCompletionFlag{help},
171 completionSpec("add", []cliCompletionFlag{help}),
172 completionSpecWithAliases("remove", []string{"rm"}, []cliCompletionFlag{help}),
173 completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{help}),
174 ),
175 completionSpec("serve", []cliCompletionFlag{help},
176 completionSpec("start", []cliCompletionFlag{completionFlag("--workspace -n", cliCompletionStaticValue), help}),
177 completionSpec("stop", []cliCompletionFlag{completionFlag("--workspace -n", cliCompletionStaticValue), help}),
178 completionSpec("status", []cliCompletionFlag{completionFlag("--workspace -n", cliCompletionStaticValue), help}),
179 completionSpec("logs", []cliCompletionFlag{completionFlag("--workspace -n", cliCompletionStaticValue), help}),
180 ),
181 completionSpec("fs", []cliCompletionFlag{help},
182 completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{help}),
183 completionSpec("get", []cliCompletionFlag{help}),
184 completionSpec("put", []cliCompletionFlag{help}),
185 ),
186 completionSpec("attach-workspace", []cliCompletionFlag{completionFlag("--stdio", cliCompletionNoValue), completionFlag("--workspace", cliCompletionStaticValue), help}),
187 ),
188 completionSpec("plugin", []cliCompletionFlag{help},
189 completionSpec("install", []cliCompletionFlag{
190 completionFlag("--yes --dry-run --link --replace", cliCompletionNoValue),
191 completionFlag("--name", cliCompletionStaticValue), help,
192 }),
193 completionSpec("list", []cliCompletionFlag{help}),
194 completionSpec("show", []cliCompletionFlag{help}),
195 completionSpecWithAliases("remove", []string{"uninstall"}, []cliCompletionFlag{completionFlag("--yes", cliCompletionNoValue), help}),
196 completionSpec("enable", []cliCompletionFlag{help}),
197 completionSpec("disable", []cliCompletionFlag{help}),
198 completionSpec("doctor", []cliCompletionFlag{help}),
199 ),
200 completionSpec("subagent", []cliCompletionFlag{help},
201 completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{completionFlag("--dir", cliCompletionPathValue), help}),
202 completionSpecWithAliases("create", []string{"new"}, append(subagentCompletionFlags(model, effort, help), completionFlag("--scope", cliCompletionStaticValue, "project", "global"))),
203 completionSpecWithAliases("edit", []string{"update"}, subagentCompletionFlags(model, effort, help)),
204 completionSpecWithAliases("delete", []string{"remove", "rm"}, []cliCompletionFlag{
205 completionFlag("--yes", cliCompletionNoValue), completionFlag("--dir", cliCompletionPathValue), help,
206 }),
207 completionSpec("try", []cliCompletionFlag{model, completionFlag("--max-steps --dir", cliCompletionStaticValue), help}),
208 completionSpec("run", []cliCompletionFlag{model, completionFlag("--max-steps --dir", cliCompletionStaticValue), help}),
209 ),
210 completionSpec("doctor", []cliCompletionFlag{completionFlag("--json", cliCompletionNoValue), help},
211 completionSpec("repair", []cliCompletionFlag{
212 completionFlag("--root", cliCompletionStaticValue), completionFlag("--apply --project --json", cliCompletionNoValue), help,
213 }),
214 completionSpec("quality", []cliCompletionFlag{completionFlag("--json", cliCompletionNoValue), help}),
215 completionSpec("session", []cliCompletionFlag{completionFlag("--zip", cliCompletionNoValue), completionFlag("--out", cliCompletionStaticValue), help}),
216 completionSpec("redact-sessions", []cliCompletionFlag{completionFlag("--dry-run --json", cliCompletionNoValue), completionFlag("--dir", cliCompletionPathValue), help}),
217 completionSpec("capabilities", []cliCompletionFlag{
218 completionFlag("--root --timeout", cliCompletionStaticValue), completionFlag("--json --live", cliCompletionNoValue), help,
219 }),
220 ),
221 completionSpec("report", []cliCompletionFlag{help},
222 completionSpec("list", []cliCompletionFlag{help}),
223 completionSpec("show", []cliCompletionFlag{help}),
224 completionSpec("send", []cliCompletionFlag{help}),
225 completionSpec("delete", []cliCompletionFlag{help}),
226 ),
227 completionSpec("session", []cliCompletionFlag{help},
228 completionSpec("list", machineSessionCompletionFlags(help)),
229 completionSpec("show", machineSessionCompletionFlags(help)),
230 completionSpec("status", machineSessionCompletionFlags(help)),
231 completionSpec("recovery", machineSessionCompletionFlags(help)),
232 ),
233 completionSpecWithAliases("hook", []string{"hooks"}, []cliCompletionFlag{help},
234 completionSpec("list", hookCompletionFlags(help)),
235 completionSpec("status", hookCompletionFlags(help)),
236 ),
237 completionSpec("task", []cliCompletionFlag{help},
238 // Machine list/show: --json --dir --project-root --session (task_machine.go).
239 completionSpec("list", completionTaskMachineListFlags(help)),
240 completionSpec("show", completionTaskMachineShowFlags(help)),
241 // Live monitor surface (task.go FlagSets).
242 completionSpec("monitor", []cliCompletionFlag{help},
243 completionSpec("list", completionTaskListFlags(help)),
244 completionSpec("status", completionTaskStatusFlags(help)),
245 completionSpec("events", completionTaskEventsFlags(help)),
246 completionSpec("stop", completionTaskControlFlags(help)),
247 completionSpec("cancel", completionTaskControlFlags(help)),
248 completionSpec("requeue", completionTaskRequeueFlags(help)),
249 completionSpec("open-session", completionTaskOpenSessionFlags(help)),
250 ),
251 completionSpec("status", completionTaskStatusFlags(help)),
252 completionSpec("events", completionTaskEventsFlags(help)),
253 completionSpec("stop", completionTaskControlFlags(help)),
254 completionSpec("cancel", completionTaskControlFlags(help)),
255 completionSpec("requeue", completionTaskRequeueFlags(help)),
256 completionSpec("open-session", completionTaskOpenSessionFlags(help)),
257 completionSpec("tmux", []cliCompletionFlag{help},
258 completionSpec("attach", completionTaskTmuxAttachFlags(help)),
259 completionSpec("status", completionTaskTmuxFlags(help)),
260 completionSpec("open", completionTaskTmuxFlags(help)),
261 completionSpec("detach", completionTaskTmuxFlags(help)),
262 ),
263 ),
264 completionSpec("review", []cliCompletionFlag{
265 completionFlag("--base --commit --instructions", cliCompletionStaticValue), model, help,
266 }),
267 completionSpec("bot", []cliCompletionFlag{help},
268 completionSpec("start", []cliCompletionFlag{completionFlag("--channels --dir", cliCompletionStaticValue), model, help}),
269 completionSpec("doctor", []cliCompletionFlag{completionFlag("--json --deep", cliCompletionNoValue), help}),
270 completionSpec("pairing", []cliCompletionFlag{help},
271 completionSpec("list", []cliCompletionFlag{help}),
272 completionSpec("approve", []cliCompletionFlag{help}),
273 completionSpecWithAliases("reject", []string{"deny"}, []cliCompletionFlag{help}),
274 ),
275 completionSpec("weixin-login", []cliCompletionFlag{completionFlag("--timeout", cliCompletionStaticValue), help}),
276 ),
277 completionSpecWithAliases("upgrade", []string{"update"}, []cliCompletionFlag{
278 completionFlag("--check --force", cliCompletionNoValue), completionFlag("--channel", cliCompletionStaticValue), help,
279 }),
280 completionSpec("completion", []cliCompletionFlag{help},
281 completionSpec("bash", nil),
282 completionSpec("zsh", nil),
283 completionSpec("fish", nil),
284 ),
285 completionSpec("version", []cliCompletionFlag{
286 completionFlag("--verbose --json", cliCompletionNoValue), help,
287 }),
288 completionSpec("docs-manifest", []cliCompletionFlag{help}),
289 completionSpec("help", []cliCompletionFlag{help}),
290 }
291 return root
292 }
293
294 func subagentCompletionFlags(model, effort, help cliCompletionFlag) []cliCompletionFlag {
295 return []cliCompletionFlag{
296 completionFlag("--description --prompt --prompt-file --tools --color --dir", cliCompletionStaticValue),
297 model, effort, help,
298 }
299 }
300
301 func machineSessionCompletionFlags(help cliCompletionFlag) []cliCompletionFlag {
302 return []cliCompletionFlag{
303 completionFlag("--json", cliCompletionNoValue),
304 completionFlag("--dir --project-root", cliCompletionStaticValue), help,
305 }
306 }
307
308 func hookCompletionFlags(help cliCompletionFlag) []cliCompletionFlag {
309 return []cliCompletionFlag{
310 completionFlag("--json", cliCompletionNoValue),
311 completionFlag("--dir --project-root --home-dir", cliCompletionStaticValue), help,
312 }
313 }
314
315 // completionTask*Flags mirror the real FlagSets in task.go / task_machine.go.
316 // Names are prefixed with completion to avoid clashing with task.go helpers.
317
318 func completionTaskMachineListFlags(help cliCompletionFlag) []cliCompletionFlag {
319 return []cliCompletionFlag{
320 completionFlag("--json", cliCompletionNoValue),
321 completionFlag("--dir --project-root", cliCompletionPathValue),
322 completionFlag("--session", cliCompletionStaticValue), help,
323 }
324 }
325
326 func completionTaskMachineShowFlags(help cliCompletionFlag) []cliCompletionFlag {
327 return completionTaskMachineListFlags(help)
328 }
329
330 func completionTaskListFlags(help cliCompletionFlag) []cliCompletionFlag {
331 return []cliCompletionFlag{
332 completionFlag("--json", cliCompletionNoValue),
333 completionFlag("--dir", cliCompletionPathValue), help,
334 }
335 }
336
337 func completionTaskStatusFlags(help cliCompletionFlag) []cliCompletionFlag {
338 return completionTaskListFlags(help)
339 }
340
341 func completionTaskEventsFlags(help cliCompletionFlag) []cliCompletionFlag {
342 return []cliCompletionFlag{
343 completionFlag("--json --jsonl --follow", cliCompletionNoValue),
344 completionFlag("--dir", cliCompletionPathValue),
345 completionFlag("--after", cliCompletionStaticValue), help,
346 }
347 }
348
349 func completionTaskControlFlags(help cliCompletionFlag) []cliCompletionFlag {
350 return []cliCompletionFlag{
351 completionFlag("--json", cliCompletionNoValue),
352 completionFlag("--dir", cliCompletionPathValue),
353 completionFlag("--expected-version --reason --idempotency-key", cliCompletionStaticValue), help,
354 }
355 }
356
357 func completionTaskRequeueFlags(help cliCompletionFlag) []cliCompletionFlag {
358 return []cliCompletionFlag{
359 completionFlag("--json", cliCompletionNoValue),
360 completionFlag("--dir", cliCompletionPathValue),
361 completionFlag("--expected-version --idempotency-key", cliCompletionStaticValue), help,
362 }
363 }
364
365 func completionTaskOpenSessionFlags(help cliCompletionFlag) []cliCompletionFlag {
366 return completionTaskListFlags(help)
367 }
368
369 func completionTaskTmuxFlags(help cliCompletionFlag) []cliCompletionFlag {
370 return completionTaskListFlags(help)
371 }
372
373 func completionTaskTmuxAttachFlags(help cliCompletionFlag) []cliCompletionFlag {
374 return []cliCompletionFlag{
375 completionFlag("--json", cliCompletionNoValue),
376 completionFlag("--dir", cliCompletionPathValue),
377 completionFlag("--session", cliCompletionStaticValue), help,
378 }
379 }
380
381 func completionCommand(args []string) int {
382 if len(args) == 0 || args[0] == "--help" || args[0] == "-h" || args[0] == "help" {
383 completionUsage(os.Stdout)
384 if len(args) == 0 {
385 return 2
386 }
387 return 0
388 }
389 if args[0] == "__complete" {
390 return completionCandidateCommand(args[1:])
391 }
392 if len(args) != 1 {
393 completionUsage(os.Stderr)
394 return 2
395 }
396 var script string
397 switch args[0] {
398 case "bash":
399 script = bashCompletionScript
400 case "zsh":
401 script = zshCompletionScript
402 case "fish":
403 script = fishCompletionScript
404 default:
405 fmt.Fprintf(os.Stderr, "unknown shell %q (want bash, zsh, or fish)\n", args[0])
406 return 2
407 }
408 fmt.Print(script)
409 return 0
410 }
411
412 func completionUsage(w *os.File) {
413 fmt.Fprintln(w, `Usage:
414 reasonix completion bash
415 reasonix completion zsh
416 reasonix completion fish
417
418 The command prints a completion script to stdout. Source it directly or save it
419 in your shell's completion directory.`)
420 }
421
422 func completionCandidateCommand(args []string) int {
423 if len(args) < 1 {
424 return 2
425 }
426 cword, err := strconv.Atoi(args[0])
427 if err != nil || cword < 0 {
428 return 2
429 }
430 for _, candidate := range cliCompletionCandidates(cword, args[1:]) {
431 if candidate == "" || strings.ContainsAny(candidate, "\r\n") {
432 continue
433 }
434 fmt.Println(candidate)
435 }
436 return 0
437 }
438
439 func cliCompletionCandidates(cword int, words []string) []string {
440 return cliCompletionCandidatesWithValues(cliCompletionRootSpec(), cword, words, runtimeCompletionValues)
441 }
442
443 func cliCompletionCandidatesWithValues(root cliCompletionSpec, cword int, words []string, dynamic func(cliCompletionValueKind) []string) []string {
444 if cword < 0 {
445 return nil
446 }
447 current := ""
448 if cword < len(words) {
449 current = words[cword]
450 }
451 limit := cword
452 if limit > len(words) {
453 limit = len(words)
454 }
455
456 ctx := &root
457 positionalSeen := false
458 var awaiting *cliCompletionFlag
459 for i := 1; i < limit; i++ {
460 token := words[i]
461 if awaiting != nil {
462 // Optional values may be omitted: a following flag-looking token is
463 // treated as the next flag rather than the optional value.
464 if awaiting.kind == cliCompletionOptionalValue && strings.HasPrefix(token, "-") && !strings.HasPrefix(token, "---") {
465 awaiting = nil
466 // fall through and parse token as a flag/subcommand
467 } else {
468 awaiting = nil
469 continue
470 }
471 }
472 if flag, inline := cliCompletionLookupFlag(ctx, token); flag != nil {
473 if flag.kind != cliCompletionNoValue && !inline {
474 awaiting = flag
475 }
476 continue
477 }
478 if strings.HasPrefix(token, "-") {
479 continue
480 }
481 if !positionalSeen {
482 if child := cliCompletionLookupSubcommand(ctx, token); child != nil {
483 ctx = child
484 continue
485 }
486 }
487 positionalSeen = true
488 }
489
490 if awaiting != nil {
491 if awaiting.kind == cliCompletionPathValue {
492 // Empty candidates → shell default file completion (bash -o default, zsh _default, fish without -f).
493 return nil
494 }
495 if awaiting.kind == cliCompletionOptionalValue && strings.HasPrefix(current, "-") {
496 // Completing another flag after optional-value flag with no value typed.
497 var out []string
498 for _, flag := range ctx.flags {
499 out = append(out, flag.names...)
500 }
501 return filterCompletionPrefix(stableUniqueCompletionValues(out), current)
502 }
503 return filterCompletionPrefix(stableUniqueCompletionValues(completionFlagValues(*awaiting, dynamic)), current)
504 }
505 if name, value, ok := strings.Cut(current, "="); ok {
506 if flag, _ := cliCompletionLookupFlag(ctx, name); flag != nil && flag.kind != cliCompletionNoValue {
507 values := completionFlagValues(*flag, dynamic)
508 out := make([]string, 0, len(values))
509 for _, candidate := range filterCompletionPrefix(values, value) {
510 out = append(out, name+"="+candidate)
511 }
512 return out
513 }
514 }
515 if strings.HasPrefix(current, "-") {
516 var out []string
517 for _, flag := range ctx.flags {
518 out = append(out, flag.names...)
519 }
520 return filterCompletionPrefix(stableUniqueCompletionValues(out), current)
521 }
522 if positionalSeen {
523 return nil
524 }
525 var out []string
526 for i := range ctx.subcommands {
527 out = append(out, ctx.subcommands[i].name)
528 }
529 return filterCompletionPrefix(stableUniqueCompletionValues(out), current)
530 }
531
532 func cliCompletionLookupFlag(spec *cliCompletionSpec, token string) (*cliCompletionFlag, bool) {
533 name := token
534 inline := false
535 if before, _, ok := strings.Cut(token, "="); ok {
536 name = before
537 inline = true
538 }
539 for i := range spec.flags {
540 for _, candidate := range spec.flags[i].names {
541 if candidate == name {
542 return &spec.flags[i], inline
543 }
544 }
545 }
546 return nil, inline
547 }
548
549 func cliCompletionLookupSubcommand(spec *cliCompletionSpec, token string) *cliCompletionSpec {
550 for i := range spec.subcommands {
551 child := &spec.subcommands[i]
552 if child.name == token {
553 return child
554 }
555 for _, alias := range child.aliases {
556 if alias == token {
557 return child
558 }
559 }
560 }
561 return nil
562 }
563
564 func completionFlagValues(flag cliCompletionFlag, dynamic func(cliCompletionValueKind) []string) []string {
565 switch flag.kind {
566 case cliCompletionStaticValue:
567 return stableUniqueCompletionValues(flag.values)
568 case cliCompletionOptionalValue:
569 // Interactive --resume [QUERY]: static values (if any) plus dynamic session IDs.
570 // Used for both separated and --resume=QUERY forms.
571 out := append([]string{}, flag.values...)
572 out = append(out, dynamic(cliCompletionSessionValue)...)
573 return stableUniqueCompletionValues(out)
574 case cliCompletionPathValue:
575 return nil
576 case cliCompletionModelValue, cliCompletionSessionValue:
577 return stableUniqueCompletionValues(dynamic(flag.kind))
578 default:
579 return nil
580 }
581 }
582
583 func runtimeCompletionValues(kind cliCompletionValueKind) []string {
584 switch kind {
585 case cliCompletionModelValue:
586 return modelRefs()
587 case cliCompletionSessionValue:
588 return completionSessionIDs()
589 default:
590 return nil
591 }
592 }
593
594 func completionSessionIDs() []string {
595 ordered, err := agent.ListSessionOrder(resolveCLISessionDir())
596 if err != nil {
597 return nil
598 }
599 out := make([]string, 0, len(ordered))
600 for _, session := range ordered {
601 out = append(out, agent.BranchID(session.Path))
602 }
603 return stableUniqueCompletionValues(out)
604 }
605
606 func filterCompletionPrefix(values []string, prefix string) []string {
607 out := make([]string, 0, len(values))
608 for _, value := range values {
609 if strings.HasPrefix(value, prefix) {
610 out = append(out, value)
611 }
612 }
613 return out
614 }
615
616 func stableUniqueCompletionValues(values []string) []string {
617 seen := make(map[string]bool, len(values))
618 out := make([]string, 0, len(values))
619 for _, value := range values {
620 value = strings.TrimSpace(value)
621 if value == "" || seen[value] {
622 continue
623 }
624 seen[value] = true
625 out = append(out, value)
626 }
627 return out
628 }
629
630 const bashCompletionScript = `# bash completion for reasonix
631 _reasonix_completion() {
632 local line
633 COMPREPLY=()
634 while IFS= read -r line; do
635 COMPREPLY+=("$line")
636 done < <(command reasonix completion __complete "$COMP_CWORD" "${COMP_WORDS[@]}" 2>/dev/null)
637 }
638 complete -o default -F _reasonix_completion reasonix
639 `
640
641 const zshCompletionScript = `#compdef reasonix
642 _reasonix_completion() {
643 local -a candidates
644 candidates=("${(@f)$(command reasonix completion __complete "$((CURRENT - 1))" "${words[@]}" 2>/dev/null)}")
645 if (( ${#candidates[@]} )); then
646 compadd -- "${candidates[@]}"
647 else
648 _default
649 fi
650 }
651 compdef _reasonix_completion reasonix
652 `
653
654 const fishCompletionScript = `function __reasonix_completion
655 set -l tokens (commandline -opc)
656 set -a tokens (commandline -ct)
657 set -l current_index (math (count $tokens) - 1)
658 command reasonix completion __complete $current_index $tokens 2>/dev/null
659 end
660 complete -c reasonix -a '(__reasonix_completion)'
661 `
662
662 lines GO