opencode-docker-panel 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to this plugin, by version and date. The version is the one in `package.json` at
4
+ that commit.
5
+
6
+ ## 0.4.8 - 2026-10-04
7
+
8
+ - The package now ships precompiled ESM: `npm run build` transpiles the sources with the same
9
+ Solid options OpenTUI's own transform uses, and `exports["./tui"]` points at `dist/tui.js`
10
+ - `react` dropped: the panel never imported it, and the host rewrites `@opentui/solid` and
11
+ `solid-js` to its own runtime when a package under `node_modules` ships JavaScript
12
+ - Peer dependencies stay optional, so npm never writes a second Solid or OpenTUI copy next to
13
+ the plugin
14
+ - `check-build` verifies the built entry imports, claims `sidebar.content`, and releases it on
15
+ cleanup
16
+
17
+ ## 0.4.1 — 2026-10-04
18
+
19
+ - `react` added to peer dependencies, so the panel loads from npm
20
+
21
+ ## 0.4.0 — 2026-10-04
22
+
23
+ - Sidebar draws running containers only, at most five, with the header opening the full list
24
+ - `Pin` and `Unpin` per container, stored by the host and surviving a TUI restart
25
+ - `Up stack` starts a compose project from the agent directory, only when the file provably declares
26
+ the clicked container's project
27
+ - An unstarted stack in the agent directory gets its own line under the rows
28
+ - Packaged for npm: `files` allowlist, peer dependencies, repository metadata, npm install docs
29
+ - Readme trimmed to what a user needs, with the working install path first
30
+
31
+ ## 0.3.0 — 2026-09-30
32
+
33
+ - `Start`, `Restart`, `Stop` and `Down compose project` per container, with `Down` confirming first
34
+ - Log view in the same dialog, `docker logs --timestamps --tail 200`, streams merged by timestamp
35
+ - Terminal recording added to the readme
36
+ - CI running typecheck and parser checks on both Linux and Windows
37
+
38
+ ## 0.2.0 — 2026-09-29
39
+
40
+ - Loads as a CLI-only plugin, no `@opencode/plugin` import at runtime
41
+ - A poll that reports no containers clears the panel instead of keeping stale rows
42
+
43
+ ## 0.1.0 — 2026-09-29
44
+
45
+ - First version: Docker containers in the `sidebar.content` slot, polled from `docker ps`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Victor Ochenin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # opencode-docker-panel
2
+
3
+ [![CI](https://github.com/victor-ochenin/opencodeDockerPlugin/actions/workflows/ci.yml/badge.svg)](https://github.com/victor-ochenin/opencodeDockerPlugin/actions/workflows/ci.yml)
4
+
5
+ A Docker container panel for the OpenCode 2 TUI sidebar.
6
+
7
+ The panel appends itself to the `sidebar.content` slot, polls `docker ps --all --format "{{json .}}"`, and lets you act on a container from the row itself. Clicking a row opens a dialog with the actions docker accepts for that container's current state; `Down` is the only one that asks for confirmation.
8
+
9
+ Русская версия этого файла: [README.ru.md](README.ru.md)
10
+
11
+ ![The Docker panel in the session sidebar, with an unstarted compose stack from the agent directory](docs/demo2.gif)
12
+
13
+ ## Contents
14
+
15
+ - [What it does](#what-it-does)
16
+ - [Compose stacks](#compose-stacks)
17
+ - [Logs](#logs)
18
+ - [What it shows](#what-it-shows)
19
+ - [Requirements](#requirements)
20
+ - [Install](#install)
21
+ - [Option A: let an LLM do it](#option-a-let-an-llm-do-it)
22
+ - [Option B: manual setup](#option-b-manual-setup)
23
+ - [From a local checkout](#from-a-local-checkout)
24
+ - [Verification](#verification)
25
+ - [Options](#options)
26
+ - [Known limits](#known-limits)
27
+ - [Changelog](#changelog)
28
+ - [License](#license)
29
+
30
+ ## What it does
31
+
32
+ ```text
33
+ Docker (2)
34
+ • api 0.0.0.0:8080->8080/tcp · shop
35
+ 1 more, click for all
36
+ Up stack · storefront is not running here
37
+ ```
38
+
39
+ | Container state | Actions offered |
40
+ |---|---|
41
+ | `running`, `restarting`, `paused` | `Restart`, `Stop`, and `Down` when the container belongs to a compose project |
42
+ | `created`, `exited`, `dead` | `Start`, `Up stack` when the agent directory holds a matching compose file, and `Down` when the container belongs to a compose project |
43
+ | anything else | none |
44
+
45
+ | Action | Command |
46
+ |---|---|
47
+ | Start | `docker start -- <name>` |
48
+ | Up stack | `docker compose -f <file> -p <project> up -d` |
49
+ | Restart | `docker restart -- <name>` |
50
+ | Stop | `docker stop -- <name>` |
51
+ | Down | `docker compose -p <project> down` |
52
+
53
+ `--` is not optional: a container name may start with a dash, and without the separator docker reads it as a flag. Every option shows its exact command in the dialog footer, so the destructive one is never a surprise.
54
+
55
+ ## Compose stacks
56
+
57
+ `Up stack` starts a whole compose project instead of one container, using the compose file that sits in the agent directory. That file is executable content: it carries `build`, `command` and `entrypoint`, which is why the dialog shows the exact command before you run it, and why `up` needs no confirmation while `down` does.
58
+
59
+ The button appears only when the file provably belongs to the project of the container you clicked. The project name is read the way compose v2 reads it: a top-level `name:` if there is one, otherwise the name of the directory holding the file. It must equal the `com.docker.compose.project` label on the container, and the container must not be running already. Anything else and the button is simply absent, because a wrong stack is worse than no button.
60
+
61
+ A stack that has no containers at all is the normal state of something never started, and it gets its own way in. When the agent directory holds a compose file and no container on the machine carries that project label, the panel adds a line under the rows:
62
+
63
+ ```text
64
+ Up stack · storefront is not running here
65
+ ```
66
+
67
+ Clicking it asks once, showing the exact command before anything runs, because a compose file is executable content and this stack has no container to name it. The line disappears as soon as one container of that project shows up, because from then on the per-container `Up stack` is the way in. Nothing is offered for a Dockerfile on its own: there is no image name to run, so the panel would be guessing.
68
+
69
+ The project is passed twice, as `-f <file>` and `-p <project>`. The name inside a compose file can be templated or overridden by the environment, and `-p` makes sure the stack you start is the one the panel was already showing rather than a second copy of it.
70
+
71
+ Candidate file names are tried in compose's own order: `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml`. The first file that exists wins even when it declares another project, because that is the file compose itself would use, so falling through to the next candidate would offer a stack the CLI ignores.
72
+
73
+ `Down` runs `docker compose down` against the compose project the container carries in its labels, which stops every container of that project and removes their networks. Volumes are kept, and the confirmation dialog says so. A container with no compose project never gets the option, and `buildArgs` refuses it as well.
74
+
75
+ The result of every action arrives as a toast, and the panel refreshes immediately instead of waiting for the next poll. While a command runs the header shows `working` and further clicks are ignored, so a double click cannot launch two commands.
76
+
77
+ ## Logs
78
+
79
+ `Logs` in the same menu replaces the dialog contents with a log view for that container, so the log and the actions share one window. It closes with `esc` or by clicking the footer line.
80
+
81
+ The view is `docker logs --timestamps --tail 200 -- <name>`, refreshed every two seconds while it is open. Docker interleaves the container's two streams, so the two buffers are parsed separately and merged back by timestamp; without that the log reads all of stdout first. ANSI sequences and lone `CR` are stripped, because the terminal would otherwise run them as control codes. A fixed window of fifteen lines is scrolled with the mouse wheel.
82
+
83
+ ## What it shows
84
+
85
+ The dot is coloured by container state: green for `running`, yellow for `paused` and `restarting`, red for `dead`, muted for everything else. The panel draws running containers and nothing else, at most five rows in the order docker reports them, so the list does not jump between polls. Stopped containers never get a row of their own: the header opens a dialog with every container, and picking one there opens the same actions as a click on its row. A `N more, click for all` line appears under the rows when something is still hidden. With rows to show, the header collapses and expands them instead.
86
+
87
+ `Pin` in a container's menu keeps that container visible whatever its state, and it spends the five row budget when it happens to run, so pinning everything shows everything. A pin is stored by the host, so it survives a TUI restart and applies to every session; `Unpin` in the same menu drops it. Pinned rows are marked `pinned` next to their ports and project.
88
+
89
+ | State | Panel output |
90
+ |---|---|
91
+ | Docker up with containers | `Docker (N)` plus one row per container |
92
+ | Docker up, nothing created | `no containers` |
93
+ | Docker Desktop not running | `docker desktop not running` |
94
+ | Docker not installed | `docker not installed` |
95
+ | `docker ps` timed out | last known rows with a `stale` marker |
96
+ | No permission on the Docker socket | `no permission to talk to docker` |
97
+
98
+ A missing or stopped Docker is a normal state, not a plugin failure, so it is reported as a muted line rather than an error. Any failed poll, whether a timeout or a dead daemon, keeps the last known rows with a `stale` marker instead of blanking the panel; a successful poll that reports no containers clears the list.
99
+
100
+ ## Requirements
101
+
102
+ - OpenCode 2 runtime with plugin slots (`opencode2`)
103
+ - `docker` on `PATH`: Docker Desktop on Windows, Docker Engine on Linux and macOS
104
+
105
+ The panel's own strings are English only; the documentation is bilingual.
106
+
107
+ ## Install
108
+
109
+ ### Option A: let an LLM do it
110
+
111
+ Paste this into any agent (Claude Code, OpenCode, Cursor, and so on):
112
+
113
+ ```text
114
+ Install the opencode-docker-panel Docker sidebar plugin by following
115
+ https://github.com/victor-ochenin/opencodeDockerPlugin#installation
116
+ ```
117
+
118
+ ### Option B: manual setup
119
+
120
+ Add the plugin to `~/.config/opencode/cli.json`. Create the file if it does not exist and keep whatever is already in it.
121
+
122
+ ```jsonc
123
+ {
124
+ "$schema": "https://opencode.ai/v2/cli.json",
125
+ "plugins": [{ "package": "opencode-docker-panel", "options": { "intervalMs": 3000 } }]
126
+ }
127
+ ```
128
+
129
+ Two things trip people up here, so they are worth stating plainly:
130
+
131
+ - **It goes in `cli.json`, not `opencode.json`.** This is a terminal-only plugin: it draws in the sidebar, and `cli.json` is the file the terminal client reads.
132
+ - **There is no login step and no provider to configure.** It talks to the local `docker` CLI and nothing else.
133
+
134
+ Restart the TUI afterwards. The host installs the package on the next start; nothing to copy and nothing to build.
135
+
136
+ Pin a version when you want a known state: `{ "package": "opencode-docker-panel@0.4.1" }`.
137
+
138
+ ### From a local checkout
139
+
140
+ To run the plugin straight from a clone instead, copy the files into the OpenCode config directory. On Windows that is `%USERPROFILE%\.config\opencode\plugins\docker-panel\`.
141
+
142
+ ```powershell
143
+ $src = "path\to\opencodeDockerPlugin"
144
+ $dst = "$HOME\.config\opencode\plugins\docker-panel"
145
+ New-Item -ItemType Directory -Force -Path $dst | Out-Null
146
+ Copy-Item "$src\*.ts","$src\*.tsx","$src\package.json" -Destination $dst -Force
147
+ ```
148
+
149
+ ```jsonc
150
+ {
151
+ "$schema": "https://opencode.ai/v2/cli.json",
152
+ "plugins": [{ "package": "./plugins/docker-panel", "options": { "intervalMs": 3000 } }]
153
+ }
154
+ ```
155
+
156
+ The package ships TypeScript sources and OpenCode resolves them at runtime, so there is no build step. `solid-js`, the OpenTUI packages and the OpenCode SDK are peer dependencies rather than hard dependencies, so a second copy of the SDK cannot shadow the one the host is running.
157
+
158
+ ### Verification
159
+
160
+ There is no CLI check for this one: the plugin draws in the terminal UI, so `opencode run` will never show it. Verify in the TUI.
161
+
162
+ 1. `docker ps` returns at least one container. If it fails, the panel has nothing to draw and says so.
163
+ 2. Restart the TUI and open a session.
164
+ 3. The sidebar gets a `Docker` header with a container count. Running containers appear as rows, at most five of them, with a `N more, click for all` line under them when something is still hidden.
165
+ 4. Collapse the list with a click on `Docker`, then click it again to open the full list. Pick a container to get its actions menu.
166
+
167
+ If the header never appears, the plugin did not load: check that the entry is in `cli.json` under `plugins`, and check `~/.local/share/opencode/log/opencode.log` for a load error.
168
+
169
+ ## Options
170
+
171
+ | Option | Default | Notes |
172
+ |---|---|---|
173
+ | `intervalMs` | `3000` | Poll interval, clamped to 1000..60000 |
174
+
175
+ ## Known limits
176
+
177
+ - The sidebar draws containers that are `running` and nothing else. A `paused` or `restarting` container gets no row even though docker still counts it as alive; pin it to see it.
178
+ - OpenCode 2 is in beta, so slot names and theme tokens may change.
179
+ - A repaint workaround resets the collapsed state and closes an open log view when the container list really changes.
180
+ - The poll spawns `docker ps` on an interval. On a host with hundreds of containers, raise `intervalMs` to 5000 or higher.
181
+ - At most five running containers get a row, and the five is a constant rather than an option: a host with thirty containers shows five and leaves the rest to the dialog. Pinning is the only way to promote a sixth.
182
+ - The sidebar itself cannot scroll, so the containers past the five live in the dialog rather than in the panel.
183
+ - No exec, no volume or image actions, no restart history. `Down` is offered per container but acts on the whole compose project. The log view is a fixed window over the last 200 lines with no history beyond that.
184
+ - `Up stack` only ever starts a stack whose compose file sits in the agent directory. A project that was created somewhere else can still be torn down with `Down`, but it can only be started with `Start` on a single container.
185
+ - `Up stack` may stay hidden when the compose file declares its project in a form the panel cannot read, such as an indented `name:` or a quoted value. That is a refusal rather than a wrong stack.
186
+ - Actions are mouse-only; the plugin registers no keymap layer.
187
+
188
+ ## Changelog
189
+
190
+ Versions and dates are in [CHANGELOG.md](CHANGELOG.md).
191
+
192
+ ## License
193
+
194
+ MIT
package/README.ru.md ADDED
@@ -0,0 +1,194 @@
1
+ # opencode-docker-panel
2
+
3
+ [![CI](https://github.com/victor-ochenin/opencodeDockerPlugin/actions/workflows/ci.yml/badge.svg)](https://github.com/victor-ochenin/opencodeDockerPlugin/actions/workflows/ci.yml)
4
+
5
+ Панель контейнеров Docker для бокового сайдбара OpenCode 2.
6
+
7
+ Плагин добавляет себя в слот `sidebar.content`, опрашивает `docker ps --all --format "{{json .}}"` и позволяет действовать с контейнером прямо из строки. Клик по строке открывает диалог с командами, которые docker принимает в текущем состоянии контейнера, и только `Down` спрашивает подтверждение.
8
+
9
+ English version of this file: [README.md](README.md)
10
+
11
+ ![Панель Docker в сайдбаре сессии с незапущенным compose-стеком из каталога агента](docs/demo2.gif)
12
+
13
+ ## Содержание
14
+
15
+ - [Что показывает панель](#что-показывает-панель)
16
+ - [Действия](#действия)
17
+ - [Compose-стек](#compose-стек)
18
+ - [Логи](#логи)
19
+ - [Требования](#требования)
20
+ - [Установка](#установка)
21
+ - [Вариант A: пусть сделает LLM](#вариант-a-пусть-сделает-llm)
22
+ - [Вариант B: вручную](#вариант-b-вручную)
23
+ - [Из локального клона](#из-локального-клона)
24
+ - [Проверка](#проверка)
25
+ - [Опции](#опции)
26
+ - [Ограничения](#ограничения)
27
+ - [Изменения](#изменения)
28
+ - [Лицензия](#лицензия)
29
+
30
+ ## Что показывает панель
31
+
32
+ ```text
33
+ Docker (2)
34
+ • api 0.0.0.0:8080->8080/tcp · shop
35
+ 1 more, click for all
36
+ Up stack · storefront is not running here
37
+ ```
38
+
39
+ Цвет точки зависит от состояния: зелёный для `running`, жёлтый для `paused` и `restarting`, красный для `dead`, приглушённый для остальных. Панель рисует только запущенные контейнеры, не больше пяти строк в том порядке, в котором их отдаёт docker, поэтому список не прыгает между опросами. Остановленным контейнерам строки не достаётся: вход в них — заголовок, он открывает диалог со всеми контейнерами, а выбор контейнера там открывает те же действия, что и клик по его строке. Строка `N more, click for all` стоит под строками, если что-то ещё скрыто. Когда строки есть, заголовок сворачивает и разворачивает их.
40
+
41
+ Пункт `Pin` в меню контейнера оставляет контейнер видимым независимо от состояния, и на запущенном он занимает место в бюджете из пяти строк, поэтому закрепив всё, видно всё. Закрепление хранит хост, поэтому оно переживает перезапуск TUI и действует во всех сессиях; пункт `Unpin` в том же меню снимает его. Закреплённые строки помечены `pinned` рядом с портами и проектом.
42
+
43
+ | Состояние панели | Что видно |
44
+ |---|---|
45
+ | Docker работает, есть контейнеры | `Docker (N)` и по строке на контейнер |
46
+ | Docker работает, контейнеров нет | `no containers` |
47
+ | Docker Desktop не запущен | `docker desktop not running` |
48
+ | Docker не установлен | `docker not installed` |
49
+ | `docker ps` не ответил вовремя | последние известные строки с пометкой `stale` |
50
+ | Нет прав на сокет Docker | `no permission to talk to docker` |
51
+
52
+ Остановленный или отсутствующий Docker — это нормальное состояние, а не поломка плагина, поэтому оно показывается приглушённой строкой, а не ошибкой. Любой неудачный опрос, будь то таймаут или мёртвый демон, сохраняет последние известные строки с пометкой `stale`; успешный опрос без контейнеров очищает список.
53
+
54
+ ## Действия
55
+
56
+ | Состояние контейнера | Какие действия предлагаются |
57
+ |---|---|
58
+ | `running`, `restarting`, `paused` | `Restart`, `Stop` и `Down`, если контейнер принадлежит compose-проекту |
59
+ | `created`, `exited`, `dead` | `Start`, `Up stack`, если в каталоге агента лежит подходящий compose-файл, и `Down`, если контейнер принадлежит compose-проекту |
60
+ | любое другое | ничего |
61
+
62
+ | Действие | Команда |
63
+ |---|---|
64
+ | Start | `docker start -- <имя>` |
65
+ | Up stack | `docker compose -f <файл> -p <проект> up -d` |
66
+ | Restart | `docker restart -- <имя>` |
67
+ | Stop | `docker stop -- <имя>` |
68
+ | Down | `docker compose -p <проект> down` |
69
+
70
+ Разделитель `--` обязателен: имя контейнера может начинаться с дефиса, и без разделителя docker примет его за флаг. Точная команда показана в подписи каждого пункта, поэтому разрушительная не выглядит сюрпризом.
71
+
72
+ `Down` выполняет `docker compose down` для compose-проекта из лейблов контейнера: останавливает все контейнеры проекта и удаляет их сети. Тома остаются, о чём и говорит диалог подтверждения. Контейнеру без compose-проекта пункт не предлагается, и `buildArgs` его тоже отклоняет.
73
+
74
+ Результат каждого действия приходит тостом, а панель обновляется сразу, не дожидаясь следующего опроса. Пока команда выполняется, в заголовке горит `working` и повторные клики игнорируются, так что двойной клик не запустит две команды.
75
+
76
+ ## Compose-стек
77
+
78
+ `Up stack` поднимает весь compose-проект, а не один контейнер, и берёт compose-файл из каталога агента. Такой файл — исполняемое содержимое: в нём бывают `build`, `command` и `entrypoint`, поэтому диалог сначала показывает точную команду и подтверждения, в отличие от `down`, не требует.
79
+
80
+ Пункт появляется только когда файл доказуемно принадлежит проекту контейнера, по которому кликнули. Имя проекта читается так же, как это делает compose v2: явное `name:` верхнего уровня, если оно есть, иначе имя каталога, где лежит файл. Оно должно совпасть с лейблом `com.docker.compose.project` контейнера, а сам контейнер не должен быть уже запущен. Во всех остальных случаях пункта просто нет, потому что не тот стек хуже, чем отсутствие кнопки.
81
+
82
+ Проект передаётся дважды, как `-f <файл>` и `-p <проект>`. Имя внутри файла может быть шаблонным или перебитым окружением, а `-p` гарантирует, что поднимется тот самый проект, который панель уже показывает, а не его вторая копия.
83
+
84
+ Стек, у которого нет ни одного контейнера, — обычное состояние того, что никогда не запускали, и для него сделана отдельная входная точка. Когда в каталоге агента лежит compose-файл и на машине нет ни одного контейнера с таким лейблом проекта, под строками появляется строка:
85
+
86
+ ```text
87
+ Up stack · storefront is not running here
88
+ ```
89
+
90
+ Клик по ней один раз спрашивает, показывая точную команду до запуска: compose-файл это исполняемое содержимое, а у стека нет контейнера, которым можно назвать действие. Как только появится хоть один контейнер этого проекта, строка исчезает: дальше входом служит `Up stack` у самого контейнера. Один Dockerfile без compose-файла ничего не предлагает: запускать нечего без имени образа, и панель догадывалась бы.
91
+
92
+ Кандидаты проверяются в том же порядке, что и у compose: `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml`. Первый существующий файл побеждает, даже если объявляет другой проект, потому что именно его использовал бы сам compose: если продолжить поиск, кнопка предложила бы стек, который CLI игнорирует.
93
+
94
+ ## Логи
95
+
96
+ Пункт `Logs` в том же меню перезаписывает содержимое окна логами контейнера, то есть логи и действия делят одно окно. Закрывается через `esc` или кликом по нижней строке.
97
+
98
+ Просмотр — это `docker logs --timestamps --tail 200 -- <имя>`, обновление раз в две секунды, пока окно открыто. Docker пишет потоки контейнера вперемешку, поэтому буферы разбираются отдельно и снова сливаются по времени: без этого лог читался бы сначала целиком из stdout. ANSI-последовательности и одиночные `CR` вырезаются, иначе терминал выполнил бы их как управляющие коды. Окно фиксированной высоты в пятнадцать строк прокручивается колесом мыши.
99
+
100
+ ## Требования
101
+
102
+ - Рантайм OpenCode 2 со слотами плагинов (`opencode2`)
103
+ - `docker` в `PATH`: Docker Desktop на Windows, Docker Engine на Linux и macOS
104
+
105
+ Строки панели только английские, документация двуязычная.
106
+
107
+ ## Установка
108
+
109
+ ### Вариант A: пусть сделает LLM
110
+
111
+ Вставь это в любого агента (Claude Code, OpenCode, Cursor и так далее):
112
+
113
+ ```text
114
+ Установи плагин панели Docker opencode-docker-panel по инструкции
115
+ https://github.com/victor-ochenin/opencodeDockerPlugin#installation
116
+ ```
117
+
118
+ ### Вариант B: вручную
119
+
120
+ Добавь плагин в `~/.config/opencode/cli.json`. Если файла нет, создай его, и всё, что в нём уже есть, сохрани.
121
+
122
+ ```jsonc
123
+ {
124
+ "$schema": "https://opencode.ai/v2/cli.json",
125
+ "plugins": [{ "package": "opencode-docker-panel", "options": { "intervalMs": 3000 } }]
126
+ }
127
+ ```
128
+
129
+ Два места, на которых обычно спотыкаются, поэтому скажу прямо:
130
+
131
+ - **Запись идёт в `cli.json`, а не в `opencode.json`.** Плагин только терминальный: он рисуется в сайдбаре, а `cli.json` читает терминальный клиент.
132
+ - **Логиниться не нужно, провайдера настраивать не нужно.** Плагин говорит только с локальным `docker` и больше ни с чем.
133
+
134
+ После этого перезапусти TUI: хост сам поставит пакет при следующем старте, копировать и собирать ничего не нужно.
135
+
136
+ Если нужна зафиксированная версия, укажи её явно: `{ "package": "opencode-docker-panel@0.4.1" }`.
137
+
138
+ ### Из локального клона
139
+
140
+ Чтобы запускать плагин прямо из клона, скопируй файлы в конфиг-каталог OpenCode. На Windows это `%USERPROFILE%\.config\opencode\plugins\docker-panel\`.
141
+
142
+ ```powershell
143
+ $src = "path\to\opencodeDockerPlugin"
144
+ $dst = "$HOME\.config\opencode\plugins\docker-panel"
145
+ New-Item -ItemType Directory -Force -Path $dst | Out-Null
146
+ Copy-Item "$src\*.ts","$src\*.tsx","$src\package.json" -Destination $dst -Force
147
+ ```
148
+
149
+ ```jsonc
150
+ {
151
+ "$schema": "https://opencode.ai/v2/cli.json",
152
+ "plugins": [{ "package": "./plugins/docker-panel", "options": { "intervalMs": 3000 } }]
153
+ }
154
+ ```
155
+
156
+ Пакет уходит исходниками на TypeScript, их резолвит сам OpenCode, поэтому собирать нечего. `solid-js`, пакеты OpenTUI и SDK OpenCode объявлены пирами, а не жёсткими зависимостями, поэтому вторая копия SDK не перекроет ту, которой работает хост.
157
+
158
+ ### Проверка
159
+
160
+ CLI-проверки у этого плагина нет: он рисуется в терминальном интерфейсе, поэтому `opencode run` его не покажет никогда. Проверяй в TUI.
161
+
162
+ 1. `docker ps` возвращает хотя бы один контейнер. Если команда падает, панели нечего рисовать, и она так и скажет.
163
+ 2. Перезапусти TUI и открой сессию.
164
+ 3. В сайдбаре появится заголовок `Docker` со счётчиком контейнеров. Запущенные контейнеры станут строками, не больше пяти, а под ними — строка `N more, click for all`, если что-то ещё скрыто.
165
+ 4. Кликни по `Docker`, чтобы свернуть список, и ещё раз, чтобы открыть полный. Выбери контейнер — откроется меню его действий.
166
+
167
+ Если заголовок так и не появился, плагин не загрузился: проверь, что запись лежит в `cli.json` в секции `plugins`, и посмотри `~/.local/share/opencode/log/opencode.log` на предмет ошибки загрузки.
168
+
169
+ ## Опции
170
+
171
+ | Опция | По умолчанию | Примечания |
172
+ |---|---|---|
173
+ | `intervalMs` | `3000` | Интервал опроса, ограничивается диапазоном 1000..60000 |
174
+
175
+ ## Ограничения
176
+
177
+ - Сайдбар рисует только контейнеры в состоянии `running`. `paused` и `restarting` строки не получают, хотя docker считает их живыми, — чтобы увидеть такой контейнер, закрепи его.
178
+ - OpenCode 2 находится в бета-фазе, поэтому имена слотов и токены темы могут измениться.
179
+ - Обходное решение с перерисовкой сбрасывает свёрнутое состояние и закрывает открытое окно логов, когда список контейнеров действительно меняется.
180
+ - Опрос порождает `docker ps` с интервалом. На машине с сотнями контейнеров подними `intervalMs` до 5000 или выше.
181
+ - Строк не больше пяти, и пять — константа, а не опция: на машине с тридцатью контейнерами панель покажет пять, а остальные останутся в диалоге. Единственный способ вывести шестой — закрепить его.
182
+ - Сам сайдбар не прокручивается, поэтому контейнеры за пределами пяти живут в диалоге, а не в панели.
183
+ - Нет exec, работы с томами и образами, истории перезапусков. `Down` предлагается у контейнера, но действует на весь compose-проект. Окно логов — фиксированное окно поверх последних 200 строк, истории глубже нет.
184
+ - `Up stack` поднимает только стек, чей compose-файл лежит в каталоге агента. Проект, созданный в другом месте, всё равно можно снести через `Down`, но поднять его можно только `Start` на одном контейнере.
185
+ - `Up stack` может остаться скрытым, когда compose-файл объявляет проект в форме, которую панель прочитать не может: например, `name:` с отступом или в кавычках. Это отказ, а не неверный запуск.
186
+ - Действия только мышью; слой клавиш плагин не регистрирует.
187
+
188
+ ## Изменения
189
+
190
+ Версии и даты в [CHANGELOG.md](CHANGELOG.md).
191
+
192
+ ## Лицензия
193
+
194
+ MIT
@@ -0,0 +1,77 @@
1
+ import { execFile } from "node:child_process";
2
+ const COMMAND = "docker";
3
+ const TIMEOUT_MS = 60000;
4
+ const MAX_BUFFER = 1024 * 1024;
5
+ export const ACTION_LABEL = {
6
+ start: "Start",
7
+ up: "Up stack",
8
+ restart: "Restart",
9
+ stop: "Stop",
10
+ down: "Down compose project"
11
+ };
12
+
13
+ /** The name is passed after `--` because a container name may start with a dash and docker would read it as a flag. */
14
+ export function buildArgs(action, container, target) {
15
+ if (action === "up") {
16
+ if (!target) throw new Error(`container ${container.name} has no resolved compose target`);
17
+ // Both flags are explicit: the project name in the file can be templated or overridden by the
18
+ // environment, and this way `up` acts on the very project the panel is already showing.
19
+ return ["compose", "-f", target.file, "-p", target.project, "up", "-d"];
20
+ }
21
+ if (action === "down") {
22
+ const project = container.composeProject;
23
+ if (!project) throw new Error(`container ${container.name} has no compose project`);
24
+ return ["compose", "-p", project, "down"];
25
+ }
26
+ return [action, "--", container.name];
27
+ }
28
+
29
+ /** Only offers what docker accepts for the current state, with the destructive action last. */
30
+ export function availableActions(container, target) {
31
+ const down = container.composeProject ? ["down"] : [];
32
+ const up = container.composeProject && target ? ["up"] : [];
33
+ switch (container.state) {
34
+ case "running":
35
+ case "restarting":
36
+ case "paused":
37
+ return ["restart", "stop", ...down];
38
+ case "created":
39
+ case "exited":
40
+ case "dead":
41
+ return ["start", ...up, ...down];
42
+ default:
43
+ return [];
44
+ }
45
+ }
46
+ function firstLine(text) {
47
+ const line = text.split(/\r?\n/).map(item => item.trim()).find(item => item.length > 0);
48
+ if (!line) return "";
49
+ return line.length > 80 ? `${line.slice(0, 77)}...` : line;
50
+ }
51
+ /**
52
+ * Runs an already-built argument list. `up` has no container behind it, so the boundary takes the
53
+ * argv rather than a container the caller would have to invent to satisfy the type.
54
+ */
55
+ export function runDockerArgs(args) {
56
+ return new Promise(resolve => {
57
+ execFile(COMMAND, [...args], {
58
+ timeout: TIMEOUT_MS,
59
+ windowsHide: true,
60
+ maxBuffer: MAX_BUFFER
61
+ }, (error, stdout, stderr) => {
62
+ if (!error) {
63
+ resolve({
64
+ ok: true,
65
+ message: firstLine(String(stdout)) || "done"
66
+ });
67
+ return;
68
+ }
69
+ const killed = error.killed === true;
70
+ const reason = firstLine(String(stderr)) || (killed ? "timed out" : error.message);
71
+ resolve({
72
+ ok: false,
73
+ message: reason || "command failed"
74
+ });
75
+ });
76
+ });
77
+ }
@@ -0,0 +1,48 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { basename, dirname, join } from "node:path";
3
+
4
+ /** compose v2's own search order, so the resolver sees the same file the CLI would pick. */
5
+ const CANDIDATES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
6
+ /** An unindented `name:` is top-level by construction, which is why a regex beats a YAML parser here. */
7
+ function declaredProject(body) {
8
+ // A UTF-8 BOM makes `^` miss the first line, which would hand back the directory name as if it
9
+ // were the project's. PowerShell and several editors write one, so strip it before matching.
10
+ const match = body.replace(/^\uFEFF/, "").match(/^name:[ \t]*(\S.*?)[ \t]*$/m);
11
+ return match?.[1]?.replace(/^["']|["']$/g, "") ?? "";
12
+ }
13
+
14
+ /**
15
+ * Finds the agent directory's compose file and reports the project it declares, if any. The file is
16
+ * read on every call because the call comes from opening a menu, not from the poll loop.
17
+ */
18
+ export function findComposeFile(agentDir) {
19
+ if (!agentDir) return null;
20
+ for (const candidate of CANDIDATES) {
21
+ const file = join(agentDir, candidate);
22
+ let body;
23
+ try {
24
+ body = readFileSync(file, "utf8");
25
+ } catch {
26
+ continue;
27
+ }
28
+ // The first file that exists wins even when its project differs, because that is the file
29
+ // compose itself would use. Falling through would act on a stack the CLI ignores.
30
+ return {
31
+ dir: agentDir,
32
+ file,
33
+ project: declaredProject(body) || basename(dirname(file))
34
+ };
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * The compose target for a container that is already part of the project, or null when the agent
41
+ * directory's file belongs to somebody else. `up` runs build and command lines from that file, so
42
+ * an uncertain match has to hide the button rather than point it at the wrong stack.
43
+ */
44
+ export function resolveComposeTarget(agentDir, project) {
45
+ const target = findComposeFile(agentDir);
46
+ if (!target || !project || target.project !== project) return null;
47
+ return target;
48
+ }