You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,11 @@
3
3
## [Unreleased]
4
4
5
5
### Added
6
+
-**Serve mode** — `construct run` now starts `opencode serve` headlessly inside the container (`docker run -d`) and connects a local client from the host. The local client is `opencode attach <url>` when `opencode` is on `$PATH`, or the system default browser as a fallback. This eliminates TUI-in-container rendering issues and lets users interact through their own local opencode setup.
7
+
-**Headless mode** — when passthrough args are provided (`construct [path] -- "message"`), `opencode run --attach <url> <args...>` is run locally instead of launching an interactive TUI.
8
+
-**`--serve-port` flag** — sets the port for the opencode HTTP server inside the container (default `4096`). Distinct from `--port` (application ports). Saved to `last-used.json` and replayed by `construct qs`.
6
9
-**Pass-through args (`--`)** — both `construct [flags] [path] -- <tool-args>` and `construct qs [path] -- <tool-args>` now forward everything after the bare `--` separator verbatim to the tool inside the container (e.g. `construct qs -- continue-session <session-id>`). Pass-through args are not persisted to last-used settings. Debug mode (`--debug`) ignores them.
10
+
-**`--client` flag** — explicitly choose the local client that connects to the opencode server: `tui` (always `opencode attach`; errors if opencode not on PATH), `web` (always opens browser directly), or omit for auto-detect (default: `opencode attach` if on PATH, browser otherwise). `--client web` is incompatible with passthrough args (headless mode requires opencode). Saved to `last-used.json` and replayed by `construct qs`.
7
11
8
12
### Fixed
9
13
-**Container startup "Permission denied" errors** — the entrypoint script's heredoc that writes `~/.config/opencode/AGENTS.md` used an unquoted delimiter, causing the shell to treat backtick-wrapped paths (`` `/workspace` ``, `` `/home/agent` ``) as command substitutions. The delimiter is now quoted (`<< 'AGENTSEOF'`), preventing the errors `/workspace: Permission denied` and `/home/agent: Permission denied` on startup.
@@ -76,6 +78,8 @@ construct --stack go --docker dind .
76
78
|`--debug`|`false`| Start an interactive shell instead of the agent (for troubleshooting) |
77
79
|`--mcp`|`false`| Activate MCP servers (e.g. `@playwright/mcp`); requires `--stack ui`, `--stack dotnet-ui`, `--stack dotnet-big-ui`, or `--stack ruby-ui` for browser automation |
78
80
|`--port`|*(none)*| Publish a container port to the host (repeatable). Accepts any format `docker run -p` supports: `3000`, `9000:3000`, `127.0.0.1:3000:3000`. |
81
+
|`--serve-port`|`4096`| Port for the opencode HTTP server inside the container. |
82
+
|`--client`|*(auto)*| Local client to connect to the opencode server: `tui` (always use `opencode attach`; error if not on PATH), `web` (always open browser), or omit for auto-detect. |
79
83
|`--version`| — | Print the construct version and exit. |
80
84
81
85
## Quickstart (`qs`)
@@ -86,7 +90,7 @@ After running `construct` at least once in a repo, replay the exact same invocat
86
90
construct qs [path]
87
91
```
88
92
89
-
`qs` restores the last `--stack`, `--docker`, `--mcp`, and all `--port` values used for that repo. Settings are stored in `~/.construct/last-used.json`.
93
+
`qs` restores the last `--stack`, `--docker`, `--mcp`, `--port`, `--serve-port`, and `--client` values used for that repo. Settings are stored in `~/.construct/last-used.json`.
90
94
91
95
You can pass extra arguments to the tool after `--`. They are forwarded verbatim and are not saved to last-used:
`construct` currently uses a hardcoded heuristic to decide how to connect the user to the running `opencode serve` server:
6
+
7
+
1. If `opencode` is on `$PATH` → run `opencode attach <url>` (TUI).
8
+
2. Otherwise → open the URL in a browser.
9
+
10
+
This is a reasonable default but gives users no control. A user who has `opencode` installed but prefers the web UI must work around the auto-detection. Conversely, a user who knows `opencode` is not on their `$PATH` gets a silent browser fallback with no error. Web and TUI should be equal citizens that the user can explicitly select.
11
+
12
+
## Solution
13
+
14
+
Add a `--client` flag to `construct` (and replayed by `construct qs`) with three valid values:
15
+
16
+
| Value | Meaning |
17
+
|---|---|
18
+
|`""` (empty, default) | Auto-detect: try `opencode attach`; fall back to browser if not found. |
19
+
|`"tui"`| Always use `opencode attach <url>`. Error if `opencode` not on `$PATH`. |
20
+
|`"web"`| Always open the browser directly; skip `opencode` check entirely. |
21
+
22
+
The flag is saved to `last-used.json` and replayed by `construct qs`. An empty string is omitted from JSON (`omitempty`), meaning old entries behave as auto (the previous default behaviour).
23
+
24
+
## Behaviour table
25
+
26
+
|`--client`|`opencode` on `$PATH`? | Passthrough args? | Result |
27
+
|---|---|---|---|
28
+
|`""` (auto) | yes | any |`opencode attach <url>`|
29
+
|`""` (auto) | no | any | browser (`xdg-open`/`open`) |
30
+
|`"tui"`| yes | any |`opencode attach <url>`|
31
+
|`"tui"`| no | any |**error**: "opencode not found on PATH; install opencode or use --client web" |
32
+
|`"web"`| either | none | browser directly |
33
+
|`"web"`| either | present |**fatal error**: "--client web is incompatible with passthrough args (headless requires opencode)" |
34
+
35
+
Any value other than `""`, `"tui"`, or `"web"` is a fatal validation error at startup.
36
+
37
+
## Persistence
38
+
39
+
`Client string \`json:"client,omitempty"\`` is added to `config.LastUsed`. An empty string is absent from JSON, so existing entries continue to behave as auto.
40
+
41
+
`SaveLastUsed` gains a `client string` parameter. All call sites are updated.
42
+
43
+
`construct qs` replays `--client <value>` in the `agentArgs` slice only when `last.Client != ""`.
44
+
45
+
## Error messages
46
+
47
+
| Condition | Message |
48
+
|---|---|
49
+
|`--client tui` and `opencode` not on PATH |`opencode not found on PATH; install opencode or use --client web`|
50
+
|`--client web` with passthrough args |`--client web is incompatible with passthrough args (headless requires opencode)`|
- The `[path]` positional and all `construct` flags are parsed from the portion **before**`--`.
20
20
- If `--` is absent the behaviour is identical to today (no change in the default case).
21
21
-`--debug` mode (`/bin/bash`) still ignores pass-through args — there is nothing useful to forward to a shell.
22
+
-`--client web` is incompatible with pass-through args: headless mode requires `opencode run --attach`, so a browser-only client cannot be used. Passing both results in a fatal error:
23
+
```
24
+
--client web is incompatible with passthrough args (headless requires opencode)
Copy file name to clipboardExpand all lines: docs/spec/quickstart-qs.md
+7-5Lines changed: 7 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@ Running `construct` requires remembering the `--stack` flag used last time for a
6
6
7
7
## Solution
8
8
9
-
Introduce a `qs` subcommand that replays the last `--stack`, `--docker`, `--mcp`, and`--port` flags used in a repository without requiring the user to re-type them.
9
+
Introduce a `qs` subcommand that replays the last `--stack`, `--docker`, `--mcp`, `--port`,`--serve-port`, and `--client` flags used in a repository without requiring the user to re-type them.
- Errors with a clear message if no previous run has been recorded for the given path.
21
-
- Replays `--docker`, `--mcp`, and all `--port` values that were used in the last recorded invocation.
21
+
- Replays `--docker`, `--mcp`, all `--port` values, `--serve-port`, and `--client` that were used in the last recorded invocation.
22
22
- For entries recorded before `--docker` was introduced (no `"docker"` key), defaults to `--docker none`.
23
+
-`--client` is only replayed when it was explicitly set (non-empty); absent/empty means auto-detect and is not added to the args.
23
24
- Anything after a bare `--` separator is forwarded verbatim to the tool inside the container and is **not** saved to last-used (see `docs/spec/passthrough-args.md`).
24
25
25
26
## Persistence
@@ -30,11 +31,12 @@ Last-used settings are stored in `~/.construct/last-used.json` as a JSON object
The `mcp` key is omitted when `false`; the `ports` key is omitted when empty; the `docker` key is omitted when empty (legacy entries without a docker mode default to `none` at replay time).
39
+
The `mcp` key is omitted when `false`; the `ports` key is omitted when empty; the `docker` key is omitted when empty (legacy entries without a docker mode default to `none` at replay time); `serve_port` is omitted when zero (defaults to `4096`); `client` is omitted when empty (defaults to auto-detect).
38
40
39
41
The file is written atomically (write to `.tmp`, then rename) with mode `0600`. The directory is created with mode `0700` if it does not exist.
0 commit comments