mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-11 23:53:19 -10:00
* feat: align chat and permissions with Harness Replace the virtualized transcript and split approval/sandbox policy with a natural-flow chat surface, stable node subscriptions, unified permission presets, precise session grants, and platform sandbox enforcement. Port the Harness-style process rows, turn footer, navigator, file presentation, and full-access confirmation while preserving Reasonix history and provider contracts. Add the Windows restricted-token sandbox and fail-closed capability reporting, together with migration, remote compatibility, documentation, and acceptance evidence. * chore: refresh repolint baseline for one-shot refactor Record the deliberate carry-forward from the transcript, remote presentation, permission, and Windows sandbox changes. The refreshed baseline also captures size and complexity reductions produced by the same extraction work. * fix: align permission refactor with CI contracts * fix: stabilize cross-platform permission CI Problem: The permission refactor left headless Windows tests on the workspace sandbox, exposed Windows-only lint findings, and let the transcript browser resolve Playwright assets from the wrong directory. Root cause: Tests used the interactive mode setter for a headless controller, direct Bash fixtures did not stamp their intended unrestricted preset, the transcript benchmark imported Playwright before normalizing its browser path, and Windows-tagged files had not been linted locally. Fix: Use the headless permission entry point, make Bash fixture intent explicit, normalize the transcript benchmark root before importing Playwright, and apply the Windows lint corrections without changing sandbox behavior. Verification: - go test ./... - cross-platform golangci-lint for linux, darwin, and windows - Windows amd64 cross-compilation for boot, builtin, and winsandbox tests - transcript Chromium browser replay for 240 and 1,000 turns - go run ./tools/repolint * test: harden native transcript and shell contracts Problem: The Linux native-scrollbar regression clicked the bottom arrow instead of the scrollbar thumb, and direct shell-contract tests inherited the new workspace-write default on Windows hosts without a sandbox backend. Root cause: The browser test used a fixed bottom-edge coordinate, while shell fixtures invoked Bash outside the controller path and therefore had no host-stamped permission preset. Fix: Compute the native thumb center from scrollbar geometry, retain CI screenshots as artifacts, and mark direct shell execution fixtures with an explicit full-access test context while preserving restricted-sandbox tests. Verification: - go test ./internal/tool/builtin -count=1 - Windows amd64 cross-compilation for internal/tool/builtin - GOOS=windows golangci-lint run --timeout=5m ./... - Chromium transcript replay for 240 and 1,000 turns - go run ./tools/repolint * perf: bound cumulative transcript mounting Problem: The 1,000-turn Linux browser replay exceeded the 500 ms long-task budget while cumulative history pages were mounted. Root cause: Transcript subtree mutations repeatedly rescanned all loaded rows, and each 60-turn history page committed its full natural-flow DOM in one frame. Fix: Observe direct chat-node additions only, retain per-row resize observation for internal content changes, enumerate direct row children, and reveal prepended history across bounded adjacent-frame commits without unmounting existing rows. Verification: frontend typecheck; chat natural-flow and scroll-controller tests; Chromium transcript replay (1,000 turns: 122.7 ms input P95, 161 ms longest task, 0 px anchor drift); repolint clean. * fix: keep transcript hydration synchronous Problem: The progressive history mount delayed initial and replacement transcript content by one frame, breaking remote hydration visibility contracts. Root cause: The bounded reveal path treated every order change like a history prepend, including an empty initial order and authoritative session replacement. Fix: Restrict progressive rendering to a contiguous non-empty suffix with newly added leading nodes; hydrate, replace, and grow the tail synchronously. Verification: remote-session-surface 101/101; frontend typecheck; chat natural-flow and scroll-controller tests; Chromium transcript replay within input and long-task budgets. * fix: stabilize transcript and Windows CI Problem: Linux transcript hydration remapped every mounted node on each history page and exceeded the 500ms long-task budget. The Windows project-session migration test also raced asynchronous catalog startup. Root cause: The order subscriber owned the full React child map even while progressive hydration intentionally retained the previous order reference. The catalog test requested reconciliation before the catalog had been published. Fix: Memoize the node-seat mapping by rendered-order identity so only committed history chunks reconcile. Wait for catalog publication before requesting the production reconcile path, and move shared catalog polling helpers out of the oversized test file. Verification: - 1,000-turn Chromium transcript benchmark: max long task about 249ms - frontend typecheck and hooks lint - project CLI migration tests, 10 repetitions - repolint and git diff checks * perf: bound cumulative transcript layout Problem: Linux 1000-turn transcript replay still exceeded 500ms after seat memoization because each history page reconciled existing seats and the turn rail forced layout of every loaded mark. Root cause: Progressive prepend kept one flat React child list, unchanged turns were reprojected, and the navigator read scroll geometry and rendered all marks after every page. Fix: Keep immutable 24-node seat chunks, cache unchanged turn projections, isolate row layout, window rail marks while retaining full loaded-turn geometry, and make benchmark waits target exact mounted anchors without weakening thresholds. Verification: - Chromium 1000-turn: max 196ms, input P95 119ms, 2407ms full mount - Full transcript test suite - Frontend build, typecheck, lint and bundle checks - Go migration tests 10x - Repository lint and diff checks * Serialize permission changes with approval commits Problem: permission revisions could change while an older approval was still recording and installing its session grant, exposing a mixed snapshot and allowing cross-version authorization. Root cause: mode changes, grant revocation, approval resolution, and permission snapshot reads used separate synchronization boundaries. Fix: establish permissionMu -> promptResolveMu -> permissionStateMu ordering, publish preset/revision/grants atomically, cancel the captured old turn outside the state mutation, and cover both legal concurrent orderings with a deterministic barrier test. Verification: go test -race ./internal/control (including five repeated targeted runs), Windows amd64 cross-compilation, and repository lint all pass. * Preserve chat nodes across history prepends Problem: loading older history repartitioned existing messages under different chunk parents, remounting their DOM and losing selection, focus, and local disclosure state. Root cause: the rendered order was grouped into position-derived SeatChunk components and regrouped whenever a prefix crossed a 24-node boundary. Fix: render every business node under one stable parent, progressively reveal only the new flat prefix, and publish the committed mounted order so navigation never advertises a missing target. Verification: transcript unit tests cover 1/5/23/24/25/48/49-node boundaries and 60 resident DOM identities; Chromium, WebKit, and Electron layout and 240/1000-turn production replays pass. * Clarify full-access host execution semantics Problem: product copy implied that Reasonix filesystem and network sandbox guarantees continued to apply after selecting full access. Root cause: permission labels were updated without consistently documenting Harness-style unsandboxed execution across the desktop, CLI, bot, Windows, and site surfaces. Fix: state that full access runs with the current OS account, retains explicit pre-launch host denies, and does not constrain behavior inside the launched process. Verification: desktop locale typecheck/build, documentation tests, site tests, and full-text consistency checks pass. * Stabilize virtual rail navigation coverage Problem: the Windows Electron layout check sometimes reached the test after the active turn had already scrolled the Harness-style virtual rail to its tail, leaving the early turn mark unmounted. Fix: explicitly scroll the rail to its loaded start before the early keyboard jump, then to its loaded tail before the final jump. Verification: Chromium reports 39 passing geometry scenarios and Electron reports 42. * Make permission ordering test platform-neutral Problem: the Windows control runner correctly reports restricted presets unavailable before its native sandbox is initialized, so the new concurrency test failed before reaching the transaction it was meant to exercise. Fix: drive the same serialized Controller mode-change path directly; platform capability rejection remains covered separately at the public preset boundary. Verification: the ordering suite passes ten times under the race detector and internal/control cross-compiles for Windows amd64.
682 lines
27 KiB
Go
682 lines
27 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"reasonix/internal/agent"
|
|
)
|
|
|
|
type cliCompletionValueKind uint8
|
|
|
|
const (
|
|
cliCompletionNoValue cliCompletionValueKind = iota
|
|
cliCompletionStaticValue
|
|
cliCompletionOptionalValue // next token may be a value or another flag (--resume [QUERY])
|
|
cliCompletionPathValue // path/file; backend returns empty so shells use default file completion
|
|
cliCompletionModelValue
|
|
cliCompletionSessionValue
|
|
)
|
|
|
|
type cliCompletionFlag struct {
|
|
names []string
|
|
kind cliCompletionValueKind
|
|
values []string
|
|
}
|
|
|
|
type cliCompletionSpec struct {
|
|
name string
|
|
aliases []string
|
|
flags []cliCompletionFlag
|
|
subcommands []cliCompletionSpec
|
|
}
|
|
|
|
func completionFlag(names string, kind cliCompletionValueKind, values ...string) cliCompletionFlag {
|
|
return cliCompletionFlag{names: strings.Fields(names), kind: kind, values: values}
|
|
}
|
|
|
|
func completionSpec(name string, flags []cliCompletionFlag, subcommands ...cliCompletionSpec) cliCompletionSpec {
|
|
return cliCompletionSpec{name: name, flags: flags, subcommands: subcommands}
|
|
}
|
|
|
|
func completionSpecWithAliases(name string, aliases []string, flags []cliCompletionFlag, subcommands ...cliCompletionSpec) cliCompletionSpec {
|
|
return cliCompletionSpec{name: name, aliases: aliases, flags: flags, subcommands: subcommands}
|
|
}
|
|
|
|
func catalogCompletionSpec(help cliCompletionFlag) cliCompletionSpec {
|
|
reindex := make([]cliCompletionSpec, 0, len(catalogCommands))
|
|
for _, command := range catalogCommands {
|
|
flags := append(slices.Clone(command.completionFlags), help)
|
|
reindex = append(reindex, completionSpec(command.name, flags))
|
|
}
|
|
return completionSpec("catalogs", []cliCompletionFlag{help}, completionSpec("reindex", []cliCompletionFlag{help}, reindex...))
|
|
}
|
|
|
|
func cliCompletionRootSpec() cliCompletionSpec {
|
|
model := completionFlag("--model", cliCompletionModelValue)
|
|
resume := completionFlag("--resume -r", cliCompletionOptionalValue) // optional QUERY
|
|
effort := completionFlag("--effort", cliCompletionStaticValue, "auto", "low", "medium", "high", "max")
|
|
permissionMode := completionFlag("--permission-mode", cliCompletionStaticValue,
|
|
"read-only", "workspace-write", "danger-full-access", "plan")
|
|
help := completionFlag("--help -h", cliCompletionNoValue)
|
|
|
|
interactiveFlags := []cliCompletionFlag{
|
|
model,
|
|
completionFlag("--max-steps", cliCompletionStaticValue),
|
|
completionFlag("--continue -c", cliCompletionNoValue),
|
|
resume,
|
|
completionFlag("--copy", cliCompletionNoValue),
|
|
completionFlag("--dir", cliCompletionPathValue),
|
|
effort, permissionMode,
|
|
completionFlag("--add-dir", cliCompletionPathValue),
|
|
completionFlag("--allowed-tools --allowedTools", cliCompletionStaticValue),
|
|
help,
|
|
}
|
|
// run/serve --resume require a value; only interactive root --resume [QUERY] is optional.
|
|
runResume := completionFlag("--resume", cliCompletionSessionValue)
|
|
runFlags := []cliCompletionFlag{
|
|
model,
|
|
completionFlag("--max-steps", cliCompletionStaticValue),
|
|
completionFlag("--show-thinking", cliCompletionNoValue),
|
|
completionFlag("--metrics", cliCompletionPathValue),
|
|
completionFlag("--dir", cliCompletionPathValue),
|
|
completionFlag("--continue -c", cliCompletionNoValue),
|
|
runResume,
|
|
completionFlag("--copy", cliCompletionNoValue),
|
|
effort, permissionMode,
|
|
completionFlag("--print -p", cliCompletionNoValue),
|
|
completionFlag("--events-jsonl", cliCompletionNoValue),
|
|
completionFlag("--output-format", cliCompletionStaticValue, "text", "json", "stream-json"),
|
|
completionFlag("--add-dir", cliCompletionPathValue),
|
|
completionFlag("--allowed-tools --allowedTools", cliCompletionStaticValue),
|
|
completionFlag("--ablate", cliCompletionStaticValue, "none", "all", "evidence", "planner", "subagent", "retrieval", "compaction"),
|
|
help,
|
|
}
|
|
root := cliCompletionSpec{name: "reasonix", flags: append([]cliCompletionFlag{
|
|
model,
|
|
completionFlag("--max-steps", cliCompletionStaticValue),
|
|
completionFlag("--print -p", cliCompletionNoValue),
|
|
completionFlag("--continue -c", cliCompletionNoValue),
|
|
resume,
|
|
completionFlag("--copy", cliCompletionNoValue),
|
|
permissionMode,
|
|
effort,
|
|
completionFlag("--dir", cliCompletionPathValue),
|
|
completionFlag("--add-dir", cliCompletionPathValue),
|
|
completionFlag("--allowed-tools --allowedTools", cliCompletionStaticValue),
|
|
completionFlag("--acp", cliCompletionNoValue),
|
|
completionFlag("--version -v", cliCompletionNoValue),
|
|
}, help)}
|
|
|
|
serveFlags := cliServeCompletionFlags(model, help)
|
|
|
|
root.subcommands = []cliCompletionSpec{
|
|
completionSpec("run", runFlags),
|
|
completionSpecWithAliases("chat", []string{"code"}, interactiveFlags),
|
|
completionSpec("serve", serveFlags),
|
|
completionSpec("web", serveFlags),
|
|
completionSpec("setup", []cliCompletionFlag{completionFlag("--local -l", cliCompletionNoValue), help}),
|
|
completionSpec("config", []cliCompletionFlag{help},
|
|
completionSpec("auto-plan", []cliCompletionFlag{completionFlag("--local", cliCompletionNoValue), help}),
|
|
completionSpec("reasoning-language", []cliCompletionFlag{completionFlag("--local", cliCompletionNoValue), help}),
|
|
completionSpec("compact-ratio", []cliCompletionFlag{completionFlag("--local", cliCompletionNoValue), help}),
|
|
completionSpec("currency", []cliCompletionFlag{completionFlag("--local", cliCompletionNoValue), help}),
|
|
completionSpec("telemetry", []cliCompletionFlag{help}),
|
|
),
|
|
completionSpec("init", []cliCompletionFlag{help}),
|
|
completionSpec("acp", []cliCompletionFlag{
|
|
model,
|
|
completionFlag("--planner", cliCompletionStaticValue, "auto", "off"),
|
|
completionFlag("--sandbox-network", cliCompletionStaticValue, "auto", "on", "off"),
|
|
completionFlag("--sandbox-bash", cliCompletionStaticValue, "auto", "enforce"),
|
|
completionFlag("--workspace-only", cliCompletionNoValue), help,
|
|
}),
|
|
completionSpec("mcp", []cliCompletionFlag{help},
|
|
completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{help}),
|
|
completionSpec("get", []cliCompletionFlag{help}),
|
|
completionSpec("add", []cliCompletionFlag{
|
|
completionFlag("--http --streamable-http --sse --env --header", cliCompletionStaticValue), help,
|
|
}),
|
|
completionSpec("enable", []cliCompletionFlag{help}),
|
|
completionSpec("disable", []cliCompletionFlag{help}),
|
|
completionSpecWithAliases("retry", []string{"connect"}, []cliCompletionFlag{help}),
|
|
completionSpec("update", []cliCompletionFlag{help}),
|
|
completionSpec("import", []cliCompletionFlag{help}),
|
|
completionSpecWithAliases("browse", []string{"search"}, []cliCompletionFlag{
|
|
completionFlag("--limit", cliCompletionStaticValue), completionFlag("--json", cliCompletionNoValue), help,
|
|
}),
|
|
completionSpec("install", []cliCompletionFlag{completionFlag("--as", cliCompletionStaticValue), help}),
|
|
completionSpecWithAliases("remove", []string{"rm"}, []cliCompletionFlag{help}),
|
|
),
|
|
completionSpec("remote", []cliCompletionFlag{help},
|
|
completionSpec("add", []cliCompletionFlag{
|
|
completionFlag("--identity --jump --workspace --serve-install --passphrase-env --password-env", cliCompletionStaticValue),
|
|
completionFlag("--use-ssh-config", cliCompletionNoValue), help,
|
|
}),
|
|
completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{help}),
|
|
completionSpecWithAliases("remove", []string{"rm"}, []cliCompletionFlag{help}),
|
|
completionSpec("import", []cliCompletionFlag{completionFlag("--all", cliCompletionNoValue), help}),
|
|
completionSpec("test", []cliCompletionFlag{help}),
|
|
completionSpecWithAliases("connect", []string{"open"}, []cliCompletionFlag{
|
|
completionFlag("--workspace --local-port", cliCompletionStaticValue),
|
|
completionFlag("--no-serve --open --forward-only", cliCompletionNoValue), help,
|
|
}),
|
|
completionSpec("status", []cliCompletionFlag{help}),
|
|
completionSpec("forward", []cliCompletionFlag{help},
|
|
completionSpec("add", []cliCompletionFlag{help}),
|
|
completionSpecWithAliases("remove", []string{"rm"}, []cliCompletionFlag{help}),
|
|
completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{help}),
|
|
),
|
|
completionSpec("serve", []cliCompletionFlag{help},
|
|
completionSpec("start", []cliCompletionFlag{completionFlag("--workspace -n", cliCompletionStaticValue), help}),
|
|
completionSpec("stop", []cliCompletionFlag{completionFlag("--workspace -n", cliCompletionStaticValue), help}),
|
|
completionSpec("status", []cliCompletionFlag{completionFlag("--workspace -n", cliCompletionStaticValue), help}),
|
|
completionSpec("logs", []cliCompletionFlag{completionFlag("--workspace -n", cliCompletionStaticValue), help}),
|
|
),
|
|
completionSpec("fs", []cliCompletionFlag{help},
|
|
completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{help}),
|
|
completionSpec("get", []cliCompletionFlag{help}),
|
|
completionSpec("put", []cliCompletionFlag{help}),
|
|
),
|
|
completionSpec("attach-workspace", []cliCompletionFlag{completionFlag("--stdio", cliCompletionNoValue), completionFlag("--workspace", cliCompletionStaticValue), help}),
|
|
),
|
|
completionSpec("plugin", []cliCompletionFlag{help},
|
|
completionSpec("install", []cliCompletionFlag{
|
|
completionFlag("--yes --dry-run --link --replace", cliCompletionNoValue),
|
|
completionFlag("--name", cliCompletionStaticValue), help,
|
|
}),
|
|
completionSpec("list", []cliCompletionFlag{help}),
|
|
completionSpec("show", []cliCompletionFlag{help}),
|
|
completionSpecWithAliases("remove", []string{"uninstall"}, []cliCompletionFlag{completionFlag("--yes", cliCompletionNoValue), help}),
|
|
completionSpec("enable", []cliCompletionFlag{help}),
|
|
completionSpec("disable", []cliCompletionFlag{help}),
|
|
completionSpec("doctor", []cliCompletionFlag{help}),
|
|
),
|
|
completionSpec("subagent", []cliCompletionFlag{help},
|
|
completionSpecWithAliases("list", []string{"ls"}, []cliCompletionFlag{completionFlag("--dir", cliCompletionPathValue), help}),
|
|
completionSpecWithAliases("create", []string{"new"}, append(subagentCompletionFlags(model, effort, help), completionFlag("--scope", cliCompletionStaticValue, "project", "global"))),
|
|
completionSpecWithAliases("edit", []string{"update"}, subagentCompletionFlags(model, effort, help)),
|
|
completionSpecWithAliases("delete", []string{"remove", "rm"}, []cliCompletionFlag{
|
|
completionFlag("--yes", cliCompletionNoValue), completionFlag("--dir", cliCompletionPathValue), help,
|
|
}),
|
|
completionSpec("try", []cliCompletionFlag{model, completionFlag("--max-steps --dir", cliCompletionStaticValue), help}),
|
|
completionSpec("run", []cliCompletionFlag{model, completionFlag("--max-steps --dir", cliCompletionStaticValue), help}),
|
|
),
|
|
doctorCompletionSpec(help),
|
|
completionSpec("sessions", []cliCompletionFlag{help},
|
|
completionSpec("reindex", []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue), completionFlag("--dir", cliCompletionPathValue), help,
|
|
}),
|
|
completionSpec("diagnose", []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue), completionFlag("--dir", cliCompletionPathValue), help,
|
|
}),
|
|
completionSpec("cleanup", []cliCompletionFlag{
|
|
completionFlag("--json --apply", cliCompletionNoValue), completionFlag("--dir", cliCompletionPathValue), help,
|
|
}),
|
|
),
|
|
catalogCompletionSpec(help),
|
|
completionSpec("report", []cliCompletionFlag{help},
|
|
completionSpec("list", []cliCompletionFlag{help}),
|
|
completionSpec("show", []cliCompletionFlag{help}),
|
|
completionSpec("send", []cliCompletionFlag{help}),
|
|
completionSpec("delete", []cliCompletionFlag{help}),
|
|
),
|
|
completionSpec("session", []cliCompletionFlag{help},
|
|
completionSpec("list", machineSessionCompletionFlags(help)),
|
|
completionSpec("show", machineSessionCompletionFlags(help)),
|
|
completionSpec("status", machineSessionCompletionFlags(help)),
|
|
completionSpec("recovery", machineSessionCompletionFlags(help)),
|
|
),
|
|
completionSpecWithAliases("hook", []string{"hooks"}, []cliCompletionFlag{help},
|
|
completionSpec("list", hookCompletionFlags(help)),
|
|
completionSpec("status", hookCompletionFlags(help)),
|
|
),
|
|
completionSpec("task", []cliCompletionFlag{help},
|
|
// Machine list/show: --json --dir --project-root --session (task_machine.go).
|
|
completionSpec("list", completionTaskMachineListFlags(help)),
|
|
completionSpec("show", completionTaskMachineShowFlags(help)),
|
|
// Live monitor surface (task.go FlagSets).
|
|
completionSpec("monitor", []cliCompletionFlag{help},
|
|
completionSpec("list", completionTaskListFlags(help)),
|
|
completionSpec("status", completionTaskStatusFlags(help)),
|
|
completionSpec("events", completionTaskEventsFlags(help)),
|
|
completionSpec("stop", completionTaskControlFlags(help)),
|
|
completionSpec("cancel", completionTaskControlFlags(help)),
|
|
completionSpec("requeue", completionTaskRequeueFlags(help)),
|
|
completionSpec("open-session", completionTaskOpenSessionFlags(help)),
|
|
),
|
|
completionSpec("status", completionTaskStatusFlags(help)),
|
|
completionSpec("events", completionTaskEventsFlags(help)),
|
|
completionSpec("stop", completionTaskControlFlags(help)),
|
|
completionSpec("cancel", completionTaskControlFlags(help)),
|
|
completionSpec("requeue", completionTaskRequeueFlags(help)),
|
|
completionSpec("open-session", completionTaskOpenSessionFlags(help)),
|
|
completionSpec("tmux", []cliCompletionFlag{help},
|
|
completionSpec("attach", completionTaskTmuxAttachFlags(help)),
|
|
completionSpec("status", completionTaskTmuxFlags(help)),
|
|
completionSpec("open", completionTaskTmuxFlags(help)),
|
|
completionSpec("detach", completionTaskTmuxFlags(help)),
|
|
),
|
|
),
|
|
completionSpec("review", []cliCompletionFlag{
|
|
completionFlag("--base --commit --instructions", cliCompletionStaticValue), model, help,
|
|
}),
|
|
completionSpec("bot", []cliCompletionFlag{help},
|
|
completionSpec("start", []cliCompletionFlag{completionFlag("--channels --dir", cliCompletionStaticValue), model, help}),
|
|
completionSpec("doctor", []cliCompletionFlag{completionFlag("--json --deep", cliCompletionNoValue), help}),
|
|
completionSpec("pairing", []cliCompletionFlag{help},
|
|
completionSpec("list", []cliCompletionFlag{help}),
|
|
completionSpec("approve", []cliCompletionFlag{help}),
|
|
completionSpecWithAliases("reject", []string{"deny"}, []cliCompletionFlag{help}),
|
|
),
|
|
completionSpec("weixin-login", []cliCompletionFlag{completionFlag("--timeout", cliCompletionStaticValue), help}),
|
|
),
|
|
completionSpecWithAliases("upgrade", []string{"update"}, []cliCompletionFlag{
|
|
completionFlag("--check --force", cliCompletionNoValue), completionFlag("--channel", cliCompletionStaticValue), help,
|
|
}),
|
|
completionSpec("completion", []cliCompletionFlag{help},
|
|
completionSpec("bash", nil),
|
|
completionSpec("zsh", nil),
|
|
completionSpec("fish", nil),
|
|
),
|
|
completionSpec("version", []cliCompletionFlag{
|
|
completionFlag("--verbose --json", cliCompletionNoValue), help,
|
|
}),
|
|
completionSpec("docs-manifest", []cliCompletionFlag{help}),
|
|
completionSpec("help", []cliCompletionFlag{help}),
|
|
}
|
|
return root
|
|
}
|
|
|
|
func doctorCompletionSpec(help cliCompletionFlag) cliCompletionSpec {
|
|
return completionSpec("doctor", []cliCompletionFlag{completionFlag("--json", cliCompletionNoValue), help},
|
|
completionSpec("sessions", []cliCompletionFlag{completionFlag("--json", cliCompletionNoValue), help}),
|
|
completionSpec("repair", []cliCompletionFlag{
|
|
completionFlag("--root", cliCompletionStaticValue), completionFlag("--apply --project --json", cliCompletionNoValue), help,
|
|
}),
|
|
completionSpec("quality", []cliCompletionFlag{completionFlag("--json", cliCompletionNoValue), help}),
|
|
completionSpec("session", []cliCompletionFlag{completionFlag("--zip", cliCompletionNoValue), completionFlag("--out", cliCompletionStaticValue), help}),
|
|
completionSpec("redact-sessions", []cliCompletionFlag{completionFlag("--dry-run --json", cliCompletionNoValue), completionFlag("--dir", cliCompletionPathValue), help}),
|
|
completionSpec("capabilities", []cliCompletionFlag{
|
|
completionFlag("--root --timeout", cliCompletionStaticValue), completionFlag("--json --live", cliCompletionNoValue), help,
|
|
}),
|
|
)
|
|
}
|
|
|
|
func cliServeCompletionFlags(model, help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
model,
|
|
completionFlag("--max-steps --addr", cliCompletionStaticValue),
|
|
// Serve/Web resume accepts file paths, not branch IDs.
|
|
completionFlag("--resume", cliCompletionPathValue),
|
|
completionFlag("--auth", cliCompletionStaticValue, "none", "token", "password"),
|
|
completionFlag("--token --password --port-file --token-file --pid-file", cliCompletionStaticValue),
|
|
completionFlag("--hash-password --behind-proxy --open --no-open", cliCompletionNoValue), help,
|
|
}
|
|
}
|
|
|
|
func subagentCompletionFlags(model, effort, help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--description --prompt --prompt-file --tools --color --dir", cliCompletionStaticValue),
|
|
model, effort, help,
|
|
}
|
|
}
|
|
|
|
func machineSessionCompletionFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue),
|
|
completionFlag("--dir --project-root", cliCompletionStaticValue), help,
|
|
}
|
|
}
|
|
|
|
func hookCompletionFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue),
|
|
completionFlag("--dir --project-root --home-dir", cliCompletionStaticValue), help,
|
|
}
|
|
}
|
|
|
|
// completionTask*Flags mirror the real FlagSets in task.go / task_machine.go.
|
|
// Names are prefixed with completion to avoid clashing with task.go helpers.
|
|
|
|
func completionTaskMachineListFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue),
|
|
completionFlag("--dir --project-root", cliCompletionPathValue),
|
|
completionFlag("--session", cliCompletionStaticValue), help,
|
|
}
|
|
}
|
|
|
|
func completionTaskMachineShowFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return completionTaskMachineListFlags(help)
|
|
}
|
|
|
|
func completionTaskListFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue),
|
|
completionFlag("--dir", cliCompletionPathValue), help,
|
|
}
|
|
}
|
|
|
|
func completionTaskStatusFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return completionTaskListFlags(help)
|
|
}
|
|
|
|
func completionTaskEventsFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--json --jsonl --follow", cliCompletionNoValue),
|
|
completionFlag("--dir", cliCompletionPathValue),
|
|
completionFlag("--after", cliCompletionStaticValue), help,
|
|
}
|
|
}
|
|
|
|
func completionTaskControlFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue),
|
|
completionFlag("--dir", cliCompletionPathValue),
|
|
completionFlag("--expected-version --reason --idempotency-key", cliCompletionStaticValue), help,
|
|
}
|
|
}
|
|
|
|
func completionTaskRequeueFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue),
|
|
completionFlag("--dir", cliCompletionPathValue),
|
|
completionFlag("--expected-version --idempotency-key", cliCompletionStaticValue), help,
|
|
}
|
|
}
|
|
|
|
func completionTaskOpenSessionFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return completionTaskListFlags(help)
|
|
}
|
|
|
|
func completionTaskTmuxFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return completionTaskListFlags(help)
|
|
}
|
|
|
|
func completionTaskTmuxAttachFlags(help cliCompletionFlag) []cliCompletionFlag {
|
|
return []cliCompletionFlag{
|
|
completionFlag("--json", cliCompletionNoValue),
|
|
completionFlag("--dir", cliCompletionPathValue),
|
|
completionFlag("--session", cliCompletionStaticValue), help,
|
|
}
|
|
}
|
|
|
|
func completionCommand(args []string) int {
|
|
if len(args) == 0 || args[0] == "--help" || args[0] == "-h" || args[0] == "help" {
|
|
completionUsage(os.Stdout)
|
|
if len(args) == 0 {
|
|
return 2
|
|
}
|
|
return 0
|
|
}
|
|
if args[0] == "__complete" {
|
|
return completionCandidateCommand(args[1:])
|
|
}
|
|
if len(args) != 1 {
|
|
completionUsage(os.Stderr)
|
|
return 2
|
|
}
|
|
var script string
|
|
switch args[0] {
|
|
case "bash":
|
|
script = bashCompletionScript
|
|
case "zsh":
|
|
script = zshCompletionScript
|
|
case "fish":
|
|
script = fishCompletionScript
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown shell %q (want bash, zsh, or fish)\n", args[0])
|
|
return 2
|
|
}
|
|
fmt.Print(script)
|
|
return 0
|
|
}
|
|
|
|
func completionUsage(w *os.File) {
|
|
fmt.Fprintln(w, `Usage:
|
|
reasonix completion bash
|
|
reasonix completion zsh
|
|
reasonix completion fish
|
|
|
|
The command prints a completion script to stdout. Source it directly or save it
|
|
in your shell's completion directory.`)
|
|
}
|
|
|
|
func completionCandidateCommand(args []string) int {
|
|
if len(args) < 1 {
|
|
return 2
|
|
}
|
|
cword, err := strconv.Atoi(args[0])
|
|
if err != nil || cword < 0 {
|
|
return 2
|
|
}
|
|
for _, candidate := range cliCompletionCandidates(cword, args[1:]) {
|
|
if candidate == "" || strings.ContainsAny(candidate, "\r\n") {
|
|
continue
|
|
}
|
|
fmt.Println(candidate)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func cliCompletionCandidates(cword int, words []string) []string {
|
|
return cliCompletionCandidatesWithValues(cliCompletionRootSpec(), cword, words, runtimeCompletionValues)
|
|
}
|
|
|
|
func cliCompletionCandidatesWithValues(root cliCompletionSpec, cword int, words []string, dynamic func(cliCompletionValueKind) []string) []string {
|
|
if cword < 0 {
|
|
return nil
|
|
}
|
|
current := ""
|
|
if cword < len(words) {
|
|
current = words[cword]
|
|
}
|
|
limit := min(cword, len(words))
|
|
|
|
ctx := &root
|
|
positionalSeen := false
|
|
var awaiting *cliCompletionFlag
|
|
for i := 1; i < limit; i++ {
|
|
token := words[i]
|
|
if awaiting != nil {
|
|
// Optional values may be omitted: a following flag-looking token is
|
|
// treated as the next flag rather than the optional value.
|
|
if awaiting.kind == cliCompletionOptionalValue && strings.HasPrefix(token, "-") && !strings.HasPrefix(token, "---") {
|
|
awaiting = nil
|
|
// fall through and parse token as a flag/subcommand
|
|
} else {
|
|
awaiting = nil
|
|
continue
|
|
}
|
|
}
|
|
if flag, inline := cliCompletionLookupFlag(ctx, token); flag != nil {
|
|
if flag.kind != cliCompletionNoValue && !inline {
|
|
awaiting = flag
|
|
}
|
|
continue
|
|
}
|
|
if strings.HasPrefix(token, "-") {
|
|
continue
|
|
}
|
|
if !positionalSeen {
|
|
if child := cliCompletionLookupSubcommand(ctx, token); child != nil {
|
|
ctx = child
|
|
continue
|
|
}
|
|
}
|
|
positionalSeen = true
|
|
}
|
|
|
|
if awaiting != nil {
|
|
if awaiting.kind == cliCompletionPathValue {
|
|
// Empty candidates → shell default file completion (bash -o default, zsh _default, fish without -f).
|
|
return nil
|
|
}
|
|
if awaiting.kind == cliCompletionOptionalValue && strings.HasPrefix(current, "-") {
|
|
// Completing another flag after optional-value flag with no value typed.
|
|
var out []string
|
|
for _, flag := range ctx.flags {
|
|
out = append(out, flag.names...)
|
|
}
|
|
return filterCompletionPrefix(stableUniqueCompletionValues(out), current)
|
|
}
|
|
return filterCompletionPrefix(stableUniqueCompletionValues(completionFlagValues(*awaiting, dynamic)), current)
|
|
}
|
|
if name, value, ok := strings.Cut(current, "="); ok {
|
|
if flag, _ := cliCompletionLookupFlag(ctx, name); flag != nil && flag.kind != cliCompletionNoValue {
|
|
values := completionFlagValues(*flag, dynamic)
|
|
out := make([]string, 0, len(values))
|
|
for _, candidate := range filterCompletionPrefix(values, value) {
|
|
out = append(out, name+"="+candidate)
|
|
}
|
|
return out
|
|
}
|
|
}
|
|
if strings.HasPrefix(current, "-") {
|
|
var out []string
|
|
for _, flag := range ctx.flags {
|
|
out = append(out, flag.names...)
|
|
}
|
|
return filterCompletionPrefix(stableUniqueCompletionValues(out), current)
|
|
}
|
|
if positionalSeen {
|
|
return nil
|
|
}
|
|
var out []string
|
|
for i := range ctx.subcommands {
|
|
out = append(out, ctx.subcommands[i].name)
|
|
}
|
|
return filterCompletionPrefix(stableUniqueCompletionValues(out), current)
|
|
}
|
|
|
|
func cliCompletionLookupFlag(spec *cliCompletionSpec, token string) (*cliCompletionFlag, bool) {
|
|
name := token
|
|
inline := false
|
|
if before, _, ok := strings.Cut(token, "="); ok {
|
|
name = before
|
|
inline = true
|
|
}
|
|
for i := range spec.flags {
|
|
if slices.Contains(spec.flags[i].names, name) {
|
|
return &spec.flags[i], inline
|
|
}
|
|
}
|
|
return nil, inline
|
|
}
|
|
|
|
func cliCompletionLookupSubcommand(spec *cliCompletionSpec, token string) *cliCompletionSpec {
|
|
for i := range spec.subcommands {
|
|
child := &spec.subcommands[i]
|
|
if child.name == token {
|
|
return child
|
|
}
|
|
if slices.Contains(child.aliases, token) {
|
|
return child
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func completionFlagValues(flag cliCompletionFlag, dynamic func(cliCompletionValueKind) []string) []string {
|
|
switch flag.kind {
|
|
case cliCompletionStaticValue:
|
|
return stableUniqueCompletionValues(flag.values)
|
|
case cliCompletionOptionalValue:
|
|
// Interactive --resume [QUERY]: static values (if any) plus dynamic session IDs.
|
|
// Used for both separated and --resume=QUERY forms.
|
|
out := append([]string{}, flag.values...)
|
|
out = append(out, dynamic(cliCompletionSessionValue)...)
|
|
return stableUniqueCompletionValues(out)
|
|
case cliCompletionPathValue:
|
|
return nil
|
|
case cliCompletionModelValue, cliCompletionSessionValue:
|
|
return stableUniqueCompletionValues(dynamic(flag.kind))
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func runtimeCompletionValues(kind cliCompletionValueKind) []string {
|
|
switch kind {
|
|
case cliCompletionModelValue:
|
|
return modelRefs()
|
|
case cliCompletionSessionValue:
|
|
return completionSessionIDs()
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func completionSessionIDs() []string {
|
|
ordered, err := agent.ListSessionOrder(resolveCLISessionDir())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
out := make([]string, 0, len(ordered))
|
|
for _, session := range ordered {
|
|
out = append(out, agent.BranchID(session.Path))
|
|
}
|
|
return stableUniqueCompletionValues(out)
|
|
}
|
|
|
|
func filterCompletionPrefix(values []string, prefix string) []string {
|
|
out := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
if strings.HasPrefix(value, prefix) {
|
|
out = append(out, value)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func stableUniqueCompletionValues(values []string) []string {
|
|
seen := make(map[string]bool, len(values))
|
|
out := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" || seen[value] {
|
|
continue
|
|
}
|
|
seen[value] = true
|
|
out = append(out, value)
|
|
}
|
|
return out
|
|
}
|
|
|
|
const bashCompletionScript = `# bash completion for reasonix
|
|
_reasonix_completion() {
|
|
local line
|
|
COMPREPLY=()
|
|
while IFS= read -r line; do
|
|
COMPREPLY+=("$line")
|
|
done < <(command reasonix completion __complete "$COMP_CWORD" "${COMP_WORDS[@]}" 2>/dev/null)
|
|
}
|
|
complete -o default -F _reasonix_completion reasonix
|
|
`
|
|
|
|
const zshCompletionScript = `#compdef reasonix
|
|
_reasonix_completion() {
|
|
local -a candidates
|
|
candidates=("${(@f)$(command reasonix completion __complete "$((CURRENT - 1))" "${words[@]}" 2>/dev/null)}")
|
|
if (( ${#candidates[@]} )); then
|
|
compadd -- "${candidates[@]}"
|
|
else
|
|
_default
|
|
fi
|
|
}
|
|
compdef _reasonix_completion reasonix
|
|
`
|
|
|
|
const fishCompletionScript = `function __reasonix_completion
|
|
set -l tokens (commandline -opc)
|
|
set -a tokens (commandline -ct)
|
|
set -l current_index (math (count $tokens) - 1)
|
|
command reasonix completion __complete $current_index $tokens 2>/dev/null
|
|
end
|
|
complete -c reasonix -a '(__reasonix_completion)'
|
|
`
|