Skip to content

Latest commit

 

History

History
466 lines (401 loc) · 21.6 KB

File metadata and controls

466 lines (401 loc) · 21.6 KB

Verified OpenSeek CLI Documentation

These examples are executed by moon cram test tests/cram. The Moon wrapper builds the native package at cmd/openseek first, then exposes the executable on PATH as openseek.exe.

openseek is the headless automation CLI: a subcommand tree under run, serve, review, subrun, mcp, and sessions (the interactive terminal UI is the separate openseek_tui binary, maintained in its own repository). Every command here is offline: it either prints help or fails argument validation before the agent contacts DeepSeek, so the suite needs no API key and makes no network calls. The live, API-backed examples live in tests/live/deepseek.md.

Top-Level Help

openseek --help is generated by argparse — the whole CLI is one command tree, so the top level lists the subcommands and the engine-shared options. It exits successfully.

$ openseek.exe --help
Usage: openseek [options] <command>

DeepSeek-backed MoonBit coding agent (headless automation CLI).

Commands:
  run       Run one task headlessly; stream JSONL events on stdout.
  serve     Session server: read JSONL commands (prompt/steer/cancel/compact/goal) from stdin.
  mcp       List configured MCP servers and the tools they expose.
  review    Read-only code review of base...HEAD; prints a JSON ReviewReport.
  subrun    INTERNAL: run one subagent kind as a child of this engine (input JSON on stdin; stdin EOF cancels).
  sessions  Manage durable sessions.

Options:
  -h, --help                             Show help information.
  --api-key <api-key>                    API key for the selected chat provider. [default: ]
  --model <model>                        Chat model: deepseek-v4-flash, deepseek-v4-pro, kimi-k2.7-code, kimi-k2.7-code-highspeed, glm-5.3, or glm-5.3-flash. [env: OPENSEEK_MODEL] [default: deepseek-v4-flash]
  --api-url <api-url>                    OpenAI-compatible chat completions endpoint. [env: OPENSEEK_API_URL] [default: ]
  --retry-attempts <retry-attempts>      Total tries per model request before giving up on a retryable failure (429, 5xx, or a transport error); 1 disables retrying. Omit for the client default. [env: OPENSEEK_RETRY_ATTEMPTS]
  --retry-backoff-ms <retry-backoff-ms>  Delay before the first model-request retry; it doubles per attempt, capped at 60s. Omit for the client default. [env: OPENSEEK_RETRY_BACKOFF_MS]
  --max-steps <max-steps>                Maximum agent steps per turn; omit to bound turns by the model's context window instead (a checkpoint summary carries each turn into the next). [env: OPENSEEK_MAX_STEPS]
  --thinking <thinking>                  Model thinking mode: no, high, or max; GLM maps no to low effort. [env: OPENSEEK_THINKING] [default: high]
  --session <session>                    Create or resume this durable session id.
  --session-root <session-root>          Directory containing durable OpenSeek sessions. [default: .openseek]

The root carries the engine-shared options (--api-key, --model, …) as globals, so any subcommand sees them. The interactive UI's own options (--prompt, --engine, --continue) live on the separate openseek_tui binary (its own repository, moonbitlang/openseek_tui).

A Misspelled Subcommand Is An Error, Not A Prompt

There is no free-form top-level prompt: a bare word that is not a subcommand is rejected by argparse rather than silently opening the UI with that word as a prompt. (argparse does not yet suggest a near-miss subcommand the way it does for options — tracked upstream in moonbitlang/core — so the message is a plain rejection for now.)

$ sh <<'EOF'
> stderr=$(mktemp)
> if env -u DEEPSEEK openseek.exe serv 2> "$stderr" >/dev/null; then echo exit-zero; else echo exit-non-zero; fi
> sed -n '1p' "$stderr"
> rm -f "$stderr"
> EOF
exit-non-zero
error: unexpected value 'serv' found; no more were expected

UI Options Are Not Top-Level Flags

Because the UI options live on the separate openseek_tui binary, a UI flag given to openseek is rejected — not silently accepted and then ignored before an engine subcommand. So openseek --prompt x run … is an error; use openseek_tui --prompt "…" for the UI, and openseek run "…" for a headless task.

$ sh <<'EOF'
> stderr=$(mktemp)
> if env DEEPSEEK=test-key openseek.exe --prompt x run TASK 2> "$stderr" >/dev/null; then echo exit-zero; else echo exit-non-zero; fi
> sed -n '1p' "$stderr"
> rm -f "$stderr"
> EOF
exit-non-zero
error: unexpected argument '--prompt' found

With no free-form positional, -- cannot smuggle a prompt: a value after it is rejected by the parser.

$ sh <<'EOF'
> stderr=$(mktemp)
> if env DEEPSEEK=test-key openseek.exe -- something 2> "$stderr" >/dev/null; then echo exit-zero; else echo exit-non-zero; fi
> sed -n '1p' "$stderr"
> rm -f "$stderr"
> EOF
exit-non-zero
error: unexpected value 'something' found; no more were expected

Bare openseek Is Rejected

openseek is automation-only: there is no default subcommand, so a bare invocation is an error (launch the interactive UI with openseek_tui).

$ sh <<'EOF'
> stderr=$(mktemp)
> if openseek.exe 2> "$stderr" >/dev/null; then echo exit-zero; else echo exit-non-zero; fi
> sed -n '1p' "$stderr"
> rm -f "$stderr"
> EOF
exit-non-zero
error: the following required argument was not provided: 'subcommand'

openseek run Runs One Task Headlessly

openseek run --help prints the options, environment variables, and defaults behind a headless run.

$ openseek.exe run --help
Usage: openseek run [options] [task...]

Run one task headlessly; stream JSONL events on stdout.

Arguments:
  task...  Task description.

Options:
  -h, --help                                                   Show help information.
  --api-key <api-key>                                          API key for the selected chat provider. [default: ]
  --model <model>                                              Chat model: deepseek-v4-flash, deepseek-v4-pro, kimi-k2.7-code, kimi-k2.7-code-highspeed, glm-5.3, or glm-5.3-flash. [env: OPENSEEK_MODEL] [default: deepseek-v4-flash]
  --api-url <api-url>                                          OpenAI-compatible chat completions endpoint. [env: OPENSEEK_API_URL] [default: ]
  --retry-attempts <retry-attempts>                            Total tries per model request before giving up on a retryable failure (429, 5xx, or a transport error); 1 disables retrying. Omit for the client default. [env: OPENSEEK_RETRY_ATTEMPTS]
  --retry-backoff-ms <retry-backoff-ms>                        Delay before the first model-request retry; it doubles per attempt, capped at 60s. Omit for the client default. [env: OPENSEEK_RETRY_BACKOFF_MS]
  --max-steps <max-steps>                                      Maximum agent steps per turn; omit to bound turns by the model's context window instead (a checkpoint summary carries each turn into the next). [env: OPENSEEK_MAX_STEPS]
  --thinking <thinking>                                        Model thinking mode: no, high, or max; GLM maps no to low effort. [env: OPENSEEK_THINKING] [default: high]
  --session <session>                                          Create or resume this durable session id.
  --session-root <session-root>                                Directory containing durable OpenSeek sessions. [default: .openseek]
  --no-session                                                 Run ephemerally: do not record this run to a durable session.
  --review-gate                                                On goal(met), audit the worktree against the goal with a review subagent and inject the findings as an advisory notice.
  --dir <dir>                                                  Workspace directory for relative paths; creates only the final path component if its parent exists. [default: .]
  --system-prompt-file <system-prompt-file>                    Read the complete system prompt from this file instead of the built-in prompt. [env: OPENSEEK_SYSTEM_PROMPT_FILE] [default: ]
  --system-prompt-addendum-file <system-prompt-addendum-file>  Append this file to the selected system prompt for prompt experiments. [env: OPENSEEK_SYSTEM_PROMPT_ADDENDUM_FILE] [default: ]
  --global-skills-dir <global-skills-dir>                      User-level skills directory advertised alongside workspace skills; empty means $HOME/.openseek/skills, or %USERPROFILE%/.openseek/skills on Windows. [env: OPENSEEK_GLOBAL_SKILLS_DIR] [default: ]
  --mcp-config <mcp-config>                                    Path to a JSON file of MCP servers ({"mcpServers": {"<name>": {"command", "args", "env"} | {"url", "headers"}}}); each server's tools (stdio subprocess or Streamable HTTP) are exposed to the agent, namespaced mcp__<server>__<tool>. Empty disables MCP. [env: OPENSEEK_MCP_CONFIG] [default: ]
  --review-deadline <review-deadline>                          Wall-clock deadline in milliseconds for one review audit (the review tool or --review-gate); default 900000 (15 minutes).
  --approval <approval>                                        What happens when a tool asks permission to run without its sandbox: never (default; refuse without asking, and do not offer the argument to the model), ask (prompt the controller over the command stream and wait), always (grant without asking). [env: OPENSEEK_APPROVAL] [default: never]
  --concurrency <concurrency>                                  Run the task in N sibling copies of --dir concurrently (best-of-N). Any explicit value, including 1, copies --dir into <dir>_run_<i> and never writes to --dir itself; omit the flag for a single in-place run. [default: 1]

--approval ask is refused here rather than accepted and then never honoured: a headless run reads no commands, so there is nobody the engine could ask, and the alternative is a turn that stops for five minutes to wait out a deadline no one was ever going to beat.

$ env DEEPSEEK=test-key openseek.exe run --approval ask "probe" 2>&1
error: --approval ask needs a controller to ask: this command reads no commands, so use never (refuse escalation) or always (grant it unasked)
[1]

The [env: OPENSEEK_APPROVAL] fallback is the same option arriving through the process environment instead of argv, so it is refused the same way. This is the one piece of main that argparse cannot pick up on its own: parse reads the process arguments itself but defaults the environment to empty, so the binary must hand it @env.get_env_vars() — and this case fails if it ever stops.

$ env DEEPSEEK=test-key OPENSEEK_APPROVAL=ask openseek.exe run "probe" 2>&1
error: --approval ask needs a controller to ask: this command reads no commands, so use never (refuse escalation) or always (grant it unasked)
[1]

API Key Is Required For Agent Runs

With no --api-key flag and no DEEPSEEK in the environment, a run reports the missing key and exits non-zero. The message names the model that wanted a key, not the variable that would have supplied one — DEEPSEEK here, KIMI for a Kimi model. Those options are hidden from --help, so this is where they are written down.

$ sh <<'EOF'
> stdout=$(mktemp)
> stderr=$(mktemp)
> if env -u DEEPSEEK -u KIMI -u OPENSEEK_MODEL openseek.exe run "summarize this project" > "$stdout" 2> "$stderr"; then echo exit-zero; else echo exit-non-zero; fi
> cat "$stderr"
> if test -s "$stdout"; then echo stdout-not-empty; else echo stdout-empty; fi
> rm -f "$stdout" "$stderr"
> EOF
exit-non-zero
error: an API key is required for deepseek-v4-flash: pass --api-key
stdout-empty

Unknown Options Are Rejected Before Task Text

The task is free-form after option parsing has stopped, but a leading option-looking token is still an option. This catches stale or misspelled flags instead of silently turning them into prompt text.

$ sh <<'EOF'
> stdout=$(mktemp)
> stderr=$(mktemp)
> if env -u DEEPSEEK openseek.exe run --xxy he > "$stdout" 2> "$stderr"; then echo exit-zero; else echo exit-non-zero; fi
> sed -n '1p' "$stderr"
> if test -s "$stdout"; then echo stdout-not-empty; else echo stdout-empty; fi
> rm -f "$stdout" "$stderr"
> EOF
exit-non-zero
error: unexpected argument '--xxy' found
stdout-empty

When the task itself must start with -, use the normal option delimiter. Here parsing succeeds and the run reaches the later API-key validation.

$ sh <<'EOF'
> stdout=$(mktemp)
> stderr=$(mktemp)
> if env -u DEEPSEEK -u KIMI -u OPENSEEK_MODEL openseek.exe run -- '--xxy he' > "$stdout" 2> "$stderr"; then echo exit-zero; else echo exit-non-zero; fi
> cat "$stderr"
> if test -s "$stdout"; then echo stdout-not-empty; else echo stdout-empty; fi
> rm -f "$stdout" "$stderr"
> EOF
exit-non-zero
error: an API key is required for deepseek-v4-flash: pass --api-key
stdout-empty

openseek sessions Is Offline

Session inspection and compaction operate on typed session files and do not require a API key for the selected chat provider. Hand-written log lines carry the 0 sentinel stamp; the summary event the compaction appends is stamped with the wall clock, so both sessions show calls strip ts to stay deterministic.

$ sh <<'EOF'
> tmp=$(mktemp -d)
> mkdir -p "$tmp/sessions/demo"
> cat > "$tmp/sessions/demo/openseek_session-demo.jsonl" <<'JSONL'
> {"version":1,"id":"demo","system_prompt":"system"}
> {"sequence":1,"ts":0,"item":{"kind":"user","payload":{"content":"hello"}}}
> {"sequence":2,"ts":0,"item":{"kind":"assistant","payload":{"content":"answer","tool_calls":[]}}}
> JSONL
> printf 'hello and answer' > "$tmp/summary.txt"
> env -u DEEPSEEK openseek.exe sessions list --session-root "$tmp" | cut -f1
> env -u DEEPSEEK openseek.exe sessions show demo --session-root "$tmp" | sed -E 's/"ts":[0-9]+,//g'
> env -u DEEPSEEK openseek.exe sessions compact demo --session-root "$tmp" --file "$tmp/summary.txt" --from 1 --to 2
> env -u DEEPSEEK openseek.exe sessions show demo --session-root "$tmp" | sed -E 's/"ts":[0-9]+,//g'
> rm -rf "$tmp"
> EOF
demo
{"version":1,"id":"demo","system_prompt":"system","events":[{"sequence":1,"item":{"kind":"user","payload":{"content":"hello"}}},{"sequence":2,"item":{"kind":"assistant","payload":{"content":"answer","tool_calls":[]}}}]}
compacted session demo events 1..2; last_sequence=3
{"version":1,"id":"demo","system_prompt":"system","events":[{"sequence":1,"item":{"kind":"user","payload":{"content":"hello"}}},{"sequence":2,"item":{"kind":"assistant","payload":{"content":"answer","tool_calls":[]}}},{"sequence":3,"item":{"kind":"summary","payload":{"content":"hello and answer","from_sequence":1,"to_sequence":2}}}]}

openseek run Records A Session By Default

openseek run "task" records its conversation to a generated cli-YYYYMMDD-HHMMSS-mmm session under --session-root (default .openseek), exactly as if --session had been passed — "what did the agent do?" is usually asked after the run, when an unrecorded answer is gone for good. The run announces the recording with a session_started event on stdout, so the id is in the event stream; afterwards the run is visible to sessions list, sessions show, the viz server, and --session <id> resumption.

This example stays offline by pointing --api-url at a closed local port: the engine names its session, durably records the user prompt, and only then fails to reach the API. The generated id's timestamp is normalized for determinism.

A refused connection is retryable, so every example below that targets the closed port pins OPENSEEK_RETRY_ATTEMPTS=1: none of them is testing the retry budget, and the suite's runtime should not track its default.

$ sh <<'EOF'
> tmp=$(mktemp -d)
> cd "$tmp"
> if env DEEPSEEK=test-key OPENSEEK_RETRY_ATTEMPTS=1 openseek.exe run --api-url "http://127.0.0.1:9/chat/completions" "say hi" > out.jsonl 2>/dev/null; then echo exit-zero; else echo exit-non-zero; fi
> grep -c '"event":"session_started"' out.jsonl
> env -u DEEPSEEK openseek.exe sessions list | cut -f1 | sed -E 's/cli-[0-9]{8}-[0-9]{6}-[0-9]{3}(-[A-Za-z0-9]+)?/cli-<stamp>/'
> rm -rf "$tmp"
> EOF
exit-non-zero
1
cli-<stamp>

The stdout stream is a protocol, not a log: events are written straight to stdout by their own writer, and the engine links no logger at all, so a logging environment variable cannot silence them. This case guards against the stream ever being routed back through one: once, events went through @xlog, and MOON_XLOG=warn dropped the whole stream — a TUI attached to that engine rendered nothing with no error to explain it.

$ sh <<'EOF'
> tmp=$(mktemp -d)
> cd "$tmp"
> for level in warn error; do
>   env DEEPSEEK=test-key OPENSEEK_RETRY_ATTEMPTS=1 MOON_XLOG=$level openseek.exe run --api-url "http://127.0.0.1:9/chat/completions" --dir "$tmp/$level" "say hi" > "out-$level.jsonl" 2>/dev/null
>   echo "$level: $(grep -c '"event":"agent_step"' "out-$level.jsonl")"
> done
> rm -rf "$tmp"
> EOF
warn: 1
error: 1

--no-session turns recording off: the same failing run leaves no session root behind.

$ sh <<'EOF'
> tmp=$(mktemp -d)
> cd "$tmp"
> env DEEPSEEK=test-key OPENSEEK_RETRY_ATTEMPTS=1 openseek.exe run --no-session --api-url "http://127.0.0.1:9/chat/completions" "say hi" >/dev/null 2>&1
> if test -d .openseek; then echo recorded; else echo ephemeral; fi
> rm -rf "$tmp"
> EOF
ephemeral

--dir Selects The Workspace Root

--dir defaults to ., but it can point a run at another workspace. When the final path component is missing and the parent exists, OpenSeek creates that one directory, logs workspace_created, and resolves the default session root under it.

$ sh <<'EOF'
> tmp=$(mktemp -d)
> mkdir -p "$tmp/parent"
> if env DEEPSEEK=test-key OPENSEEK_RETRY_ATTEMPTS=1 openseek.exe run --dir "$tmp/parent/new" --api-url "http://127.0.0.1:9/chat/completions" "say hi" > "$tmp/out.jsonl" 2>/dev/null; then echo exit-zero; else echo exit-non-zero; fi
> if test -d "$tmp/parent/new"; then echo dir-created; else echo dir-missing; fi
> grep -c '"event":"workspace_created"' "$tmp/out.jsonl"
> env -u DEEPSEEK openseek.exe sessions list --dir "$tmp/parent/new" | cut -f1 | sed -E 's/cli-[0-9]{8}-[0-9]{6}-[0-9]{3}(-[A-Za-z0-9]+)?/cli-<stamp>/'
> env -u DEEPSEEK openseek.exe sessions list --dir "$tmp/parent/fresh" > "$tmp/session-list.out"
> if test -d "$tmp/parent/fresh"; then echo session-dir-created; else echo session-dir-missing; fi
> wc -l < "$tmp/session-list.out" | tr -d ' '
> rm -rf "$tmp"
> EOF
exit-non-zero
dir-created
1
cli-<stamp>
session-dir-created
0

Asking for both recording behaviors at once is rejected before any work happens.

$ env DEEPSEEK=test-key openseek.exe run --session demo --no-session "say hi" 2>&1
error: --no-session contradicts --session; pick one behavior
[1]

openseek serve Speaks JSONL Commands On Stdin

serve turns the engine into a long-lived session server: prompts, steers, and cancels arrive as JSONL commands on stdin, and the usual event stream leaves on stdout. The command surface is testable offline — a malformed command is reported as a command_error event rather than killing the server, an idle cancel is a no-op, and stdin EOF shuts the server down cleanly (exit 0) without ever touching the network. The grep -o keeps only the stable event tag, since a line also carries the event's payload fields.

$ printf '{"command":"reboot"}\n{"command":"cancel"}\n' | env DEEPSEEK=test-key openseek.exe serve 2>/dev/null | grep -o '"event":"command_error"'
"event":"command_error"

A steer with no turn to land in is rejected rather than converted into a prompt: it can only arrive idle by racing a turn's terminal event through the pipes, and an engine must never start a turn the controller did not ask for. The rejection carries a steer_dropped event so the controller can ask the user to resubmit — and, usefully for this offline test, no turn means no network.

$ printf '{"command":"steer","text":"too late"}\n' | env DEEPSEEK=test-key openseek.exe serve 2>/dev/null | grep -o '"event":"steer_dropped"'
"event":"steer_dropped"

serve takes no positional; a stray task is rejected by the parser before anything runs.

$ sh <<'EOF'
> stderr=$(mktemp)
> if env DEEPSEEK=test-key openseek.exe serve "do something" 2> "$stderr" >/dev/null; then echo exit-zero; else echo exit-non-zero; fi
> sed -n '1p' "$stderr"
> rm -f "$stderr"
> EOF
exit-non-zero
error: unexpected value 'do something' found; no more were expected

MoonBit CLI Argument Parsing Pattern

Native MoonBit CLIs should use moonbitlang/core/argparse and call @argparse.parse(...) on a Command instead of hand-rolling argument parsing with @env.args(). This tiny package verifies the current FlagArg, PositionArg, and Matches shape.

$ sh <<'EOF'
> tmp=$(mktemp -d)
> mkdir -p "$tmp/cmd/echoargs"
> cat > "$tmp/moon.mod" <<'MOD'
> name = "example/cli"
> version = "0.1.0"
> MOD
> cat > "$tmp/cmd/echoargs/moon.pkg" <<'PKG'
> import {
>   "moonbitlang/core/argparse"
> }
> warnings = "+unnecessary_annotation"
> supported_targets = "+native"
> options(
>   "is-main": true,
> )
> PKG
> cat > "$tmp/cmd/echoargs/main.mbt" <<'MBT'
> ///|
> fn main raise {
>   let matches = @argparse.parse(
>     Command(
>       "echoargs",
>       about="Tiny argparse example.",
>       flags=[FlagArg("stdin", long="stdin", about="Read stdin.")],
>       positionals=[PositionArg("input", default_values=["-"])],
>     ),
>   )
>   let input = match matches.values.get("input") {
>     Some([value, ..]) => value
>     _ => fail("missing input")
>   }
>   let stdin = match matches.flags.get("stdin") {
>     Some(value) => value
>     None => false
>   }
>   println("input=\{input}")
>   println("stdin=\{stdin}")
> }
> MBT
> (cd "$tmp" && moon run --target native cmd/echoargs -- --stdin sample.toml)
> rm -rf "$tmp"
> EOF
input=sample.toml
stdin=true