# Configuration Every option is optional. Set them in the profile's `cordis.patch.yml`, under the plugin's entry id: ```yaml - id: run-environment config: visibility: always maxConcurrentRuns: 4 ``` `dsh --profile web --dump-config` prints the composed tree with the values in effect, which is the quickest way to confirm a change was picked up. ## Options | Key | Type | Default | Meaning | | --- | --- | --- | --- | | `visibility` | `auto` \| `always` \| `never` | `auto` | When the header control renders. | | `stateFile` | string | `$DSH_HOME/dsh-run-environment/projects.json` | Where per-project preferences are stored. | | `maxConcurrentRuns` | integer 1–64 | `8` | Runs allowed across every project before `POST /run` answers 429. | | `collectBytes` | integer ≥ 4096 | `32768` | In-memory tail kept per output stream. | | `spillBytes` | integer ≥ 65536 | `4194304` | Whole-stream spill cap per run; the full log survives past the tail. | | `graceMs` | integer 100–600000 | `5000` | SIGTERM → SIGKILL grace handed to the process provider when stopping. | | `failureWindowMs` | integer 1000–600000 | `30000` | A non-zero exit inside this window counts as a failed launch. | | `shell` | string | `$SHELL`, else `/bin/sh` | Shell used for user-defined commands (Windows: `cmd.exe`). | ### `visibility` - **`auto`** (default) — the control renders when the project has at least one command, detected or user-defined, or when preferences already exist for it. A directory nothing recognises and nothing was configured for shows nothing at all. - **`always`** — the control renders in every session. This is how a project no adapter recognises yet gets adopted: open it, choose **Add command…**, and it stays adopted because preferences then exist. - **`never`** — the control never renders, and the API reports `present: false`. Useful to keep the plugin installed but out of the way. ## Stored state Preferences live in one JSON document, keyed by absolute project directory: ```json { "version": 1, "projects": { "/Users/me/code/shop": { "defaultId": "node-npm:dev", "hidden": ["node-npm:lint"], "custom": [ { "id": "custom:start-the-database", "label": "Start the database", "command": "docker compose up -d", "icon": "database", "description": "Postgres on 5432, with the seed data loaded" } ], "overrides": { "node-npm:dev": { "label": "Arrancar el front", "icon": "sparkle", "description": "The dev server" } }, "order": ["node-npm:dev", "node-npm:build", "node-npm:test"], "updatedAt": "2026-09-17T12:00:00.000Z" } } } ``` It is plain JSON and safe to edit by hand while the harness is stopped: - `defaultId` — the command the play button runs. Reset it to `null` to fall back to the adapter ranking (`dev`, then `start`, then manifest order). - `hidden` — detected command ids the menu should not offer. They remain restorable from **Manage → Show hidden**. - `custom` — user-defined commands: the name shown in the menu, the shell line that runs, the icon, and an optional `description` that is shown when the row is hovered. `id` must be unique and is generated by the plugin when you add a command from the menu. - `overrides` — per-command adjustments to *detected* commands: a different name, a different icon, a description. Only what the user changed is stored: restoring the adapter's own label, or clearing the description, drops that part of the entry. A detected command's command line is never overridable — it belongs to the manifest. - `order` — the order the rows were dragged into. It is a hint, not the final word: the default is always displayed first and hidden commands always last, and anything missing from the list keeps its natural (manifest) order after everything that is in it. An empty list means "natural order". - `GET /commands?logs=1` folds each run's retained tail into the same answer, which is how a console reads a run and its output in one request. The plain list stays as light as it was, because the header polls it every couple of seconds. - `icon` and `overrides[].icon` — one of the ids in `ICON_IDS` (`lib/core/model.js`), served to the browser at `GET /commands` and offered by the picker in the add/edit dialogs. `"default"` is the neutral asterisk. An unknown id is refused on write (400) and repaired to `"default"` on read, so a hand-edited file degrades instead of failing. If the file stops parsing, the plugin moves it to `projects.json.corrupt` and starts empty rather than overwriting it. Projects are pruned least-recently-used past 200 entries. ## Limits and their consequences - **8 concurrent runs** — a ninth `POST /run` answers `429 too-many-runs`. Raise it if you routinely keep several stacks running from one harness. - **32 KiB tail per stream** — what the tooltip and `GET /log` see. Raise `collectBytes` if your start-up output is enormous. - **4 MiB spill per run** — the complete stream, written by the harness spill provider and reported in the `GET /log` header. Raise `spillBytes` for very chatty servers. - **5 s stop grace** — SIGTERM, then SIGKILL. A command that ignores SIGTERM is killed, not waited on forever.