dsh-plugin-container 0.0.0-stage → 0.3.0

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -2
  3. package/cordis.patch.yml +3 -0
  4. package/docs/ai-chat.png +0 -0
  5. package/docs/ai-new-session.png +0 -0
  6. package/docs/apple-container.png +0 -0
  7. package/docs/attach.png +0 -0
  8. package/docs/docker-console.png +0 -0
  9. package/docs/docker-overview.png +0 -0
  10. package/docs/docker-volumes.png +0 -0
  11. package/docs/image-architecture.png +0 -0
  12. package/docs/package-install.png +0 -0
  13. package/docs/provider-switch.png +0 -0
  14. package/docs/run-container.png +0 -0
  15. package/index.js +138 -0
  16. package/lib/client.js +3560 -0
  17. package/package.json +62 -4
  18. package/src/client/api.js +98 -0
  19. package/src/client/attach.jsx +134 -0
  20. package/src/client/components.jsx +320 -0
  21. package/src/client/hooks.js +161 -0
  22. package/src/client/i18n.js +502 -0
  23. package/src/client/icons.jsx +227 -0
  24. package/src/client/index.jsx +76 -0
  25. package/src/client/page.jsx +782 -0
  26. package/src/client/run-container.jsx +187 -0
  27. package/src/client/runs.js +182 -0
  28. package/src/client/store.js +91 -0
  29. package/src/client/styles.js +371 -0
  30. package/src/client/tabs/compose.jsx +94 -0
  31. package/src/client/tabs/console.jsx +211 -0
  32. package/src/client/tabs/containers.jsx +148 -0
  33. package/src/client/tabs/images.jsx +127 -0
  34. package/src/client/tabs/networks.jsx +91 -0
  35. package/src/client/tabs/overview.jsx +266 -0
  36. package/src/client/tabs/volumes.jsx +76 -0
  37. package/src/host/apple-cli.js +307 -0
  38. package/src/host/apple.js +471 -0
  39. package/src/host/attach.js +246 -0
  40. package/src/host/commands.js +235 -0
  41. package/src/host/docker-cli.js +405 -0
  42. package/src/host/jobs.js +245 -0
  43. package/src/host/parsers.js +484 -0
  44. package/src/host/runtime.js +285 -0
  45. package/src/host/service.js +836 -0
  46. package/src/host/tools.js +198 -0
  47. package/src/host/util.js +97 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-plugin-container contributors
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 CHANGED
@@ -1,3 +1,81 @@
1
- # Temporary Holding Version
1
+ # DSH 容器
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ DSH 左栏容器面板:管理 macOS 上的 Docker(colima / Docker Desktop / OrbStack / remote context)和 Apple `container`。
4
+
5
+ ![概览](docs/docker-overview.png)
6
+
7
+ ## 功能
8
+
9
+ - Docker / Apple container 双 provider,运行时状态、上下文切换
10
+ - 容器、镜像、卷、网络、Compose、命令
11
+ - 容器启动/停止/重启/删除,镜像 pull/inspect/delete
12
+ - 查看日志、inspect、实时 follow
13
+ - **Attach**:Docker `docker attach`;Apple `container exec`
14
+ - **Exec**:进入容器内部 shell(`docker exec -it <id> /bin/sh`)
15
+ - 镜像架构列(arm64/amd64)
16
+ - 只读 AI 工具:`container_status`、`container_list`、`container_inspect`、`container_logs`
17
+ - AI 诊断会新建独立会话,不污染当前对话
18
+
19
+ ## 要求
20
+
21
+ - macOS
22
+ - DSH 0.2.x
23
+ - Docker CLI;或安装 [Apple container](https://github.com/apple/container)
24
+ - 网络代理按本机环境配置
25
+
26
+ ## 安装
27
+
28
+ ```bash
29
+ dsh plugin --profile desktop add dsh-plugin-container
30
+ ```
31
+
32
+ 重启 DSH。安装器会自动写入 `dsh.profile.bundles`,**不需要手改 `cordis.patch.yml`**。
33
+
34
+ 本地开发版:
35
+
36
+ ```bash
37
+ node scripts/install-profile.mjs --home "$HOME/.dsh" --profile desktop
38
+ ```
39
+
40
+ 源码路径模式与 npm 包模式不要同时启用。
41
+
42
+ ## 使用
43
+
44
+ 1. 左栏打开 **容器**
45
+ 2. 顶部切换 Docker / Apple container
46
+ 3. 容器行操作:
47
+ - 启动 / 停止 / 重启 / 删除
48
+ - 日志 / Follow / Inspect
49
+ - Attach:接管容器主进程
50
+ - Exec:在容器内开一个新 shell
51
+ - 问 AI / 诊断:新建独立 DSH 会话,调用只读容器工具
52
+ 4. 镜像 tab 可查看架构,或直接用镜像启动容器
53
+
54
+ ## 安全
55
+
56
+ - 所有 Docker/Apple 命令以当前用户权限执行
57
+ - 无 shell 拼接;命令拆成 argv 直接 spawn
58
+ - 删除、stop、prune 等危险操作需要确认
59
+ - AI 工具只读,不提供 start/stop/rm/prune tool
60
+ - 日志和 inspect 被标记为不可信数据,不能作为指令执行
61
+
62
+ ## 开发
63
+
64
+ ```bash
65
+ npm install
66
+ npm run build # src/client -> lib/client.js
67
+ npm test # 46 tests
68
+ npm pack # 发布前检查
69
+ ```
70
+
71
+ 主要结构:
72
+
73
+ ```text
74
+ index.js Host 入口
75
+ cordis.patch.yml bundle 安装 patch
76
+ src/host/service.js provider 路由 + Docker
77
+ src/host/apple.js Apple container
78
+ src/host/attach.js Attach/Exec PTY
79
+ src/host/tools.js 只读 Agent Tools
80
+ src/client/ 页面、tabs、i18n、styles
81
+ ```
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: docker-sidebar
3
+ name: dsh-plugin-container
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/index.js ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Host entry for the Docker sidebar plugin.
3
+ *
4
+ * This file is loaded by the DSH Loader from an absolute path, so it imports
5
+ * only local modules and Node built-ins. The browser half is served separately
6
+ * from `lib/client.js` through the package's `dsh.client` declaration.
7
+ *
8
+ * The browser calls the same `/docker/...` envelope used by the Connection
9
+ * generic RPC channel. We mount the route ourselves because Connection's
10
+ * `rpc.handle` helper registers on the service owner's context, which does not
11
+ * inject `webServer`; the direct route below reuses `connection.admit()` for
12
+ * the same Host/Origin fence and browser authentication.
13
+ */
14
+ import { createDockerService } from './src/host/service.js'
15
+ import { registerContainerTools } from './src/host/tools.js'
16
+
17
+ export const name = 'docker-sidebar'
18
+ export const inject = ['subprocess', 'connection', 'webServer', 'tools', 'systemPrompt']
19
+
20
+ const CHANNEL = '/docker'
21
+ const MAX_BODY_BYTES = 1024 * 1024
22
+
23
+ /** @param {import('node:http').IncomingMessage} req */
24
+ function readBody(req) {
25
+ return new Promise((resolve, reject) => {
26
+ const chunks = []
27
+ let size = 0
28
+ req.on('data', (chunk) => {
29
+ size += chunk.byteLength
30
+ if (size > MAX_BODY_BYTES) {
31
+ reject(new Error('request body too large'))
32
+ req.destroy()
33
+ return
34
+ }
35
+ chunks.push(chunk)
36
+ })
37
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
38
+ req.on('error', reject)
39
+ })
40
+ }
41
+
42
+ /** @param {import('node:http').ServerResponse} res @param {number} status @param {unknown} body */
43
+ function send(res, status, body, contentType = 'application/json; charset=utf-8') {
44
+ if (res.writableEnded) return
45
+ const text = typeof body === 'string' ? body : JSON.stringify(body)
46
+ res.writeHead(status, { 'content-type': contentType, 'cache-control': 'no-store' })
47
+ res.end(text)
48
+ }
49
+
50
+ /**
51
+ * @param {import('node:http').IncomingMessage} req
52
+ * @param {import('node:http').ServerResponse} res
53
+ * @param {any} ctx
54
+ * @param {ReturnType<typeof createDockerService>} service
55
+ */
56
+ async function handleRequest(req, res, ctx, service) {
57
+ let admission
58
+ try {
59
+ admission = ctx.connection.admit(req)
60
+ } catch (error) {
61
+ send(res, 403, `forbidden: ${error instanceof Error ? error.message : String(error)}`, 'text/plain; charset=utf-8')
62
+ return
63
+ }
64
+ if ('rejection' in admission) {
65
+ send(res, admission.rejection, admission.rejection === 401 ? 'unauthorized' : 'forbidden', 'text/plain; charset=utf-8')
66
+ return
67
+ }
68
+ if (req.method !== 'POST') {
69
+ send(res, 405, 'method not allowed', 'text/plain; charset=utf-8')
70
+ return
71
+ }
72
+ const url = new URL(req.url ?? CHANNEL, 'http://dsh.internal')
73
+ const endpoint = url.pathname.startsWith(`${CHANNEL}/`) ? url.pathname.slice(CHANNEL.length + 1) : ''
74
+ if (endpoint === '' || endpoint.includes('/')) {
75
+ send(res, 404, 'not found', 'text/plain; charset=utf-8')
76
+ return
77
+ }
78
+
79
+ let message
80
+ try {
81
+ message = JSON.parse(await readBody(req))
82
+ } catch (error) {
83
+ send(res, 400, `invalid request body: ${error instanceof Error ? error.message : String(error)}`, 'text/plain; charset=utf-8')
84
+ return
85
+ }
86
+ if (
87
+ message === null ||
88
+ typeof message !== 'object' ||
89
+ message.type !== 'client-request' ||
90
+ typeof message.rpcId !== 'string' ||
91
+ typeof message.method !== 'string' ||
92
+ message.method !== endpoint
93
+ ) {
94
+ send(res, 400, 'invalid client-request envelope', 'text/plain; charset=utf-8')
95
+ return
96
+ }
97
+
98
+ const controller = new AbortController()
99
+ const abort = () => controller.abort()
100
+ res.on('close', abort)
101
+ try {
102
+ const result = await service.handle(message.method, message.payload, controller.signal, admission.peer)
103
+ send(res, 200, { type: 'server-response', rpcId: message.rpcId, result })
104
+ } catch (error) {
105
+ send(res, 500, `handler failure: ${error instanceof Error ? error.message : String(error)}`, 'text/plain; charset=utf-8')
106
+ } finally {
107
+ res.off('close', abort)
108
+ }
109
+ }
110
+
111
+ /**
112
+ * @param {import('@deepseek-ai/cordis').Context} ctx
113
+ * @param {Record<string, any>} [config]
114
+ */
115
+ export function apply(ctx, config = {}) {
116
+ const service = createDockerService({
117
+ subprocess: ctx.subprocess,
118
+ logger: ctx.logger,
119
+ config: config ?? {},
120
+ })
121
+ try {
122
+ registerContainerTools(ctx, service)
123
+ service.enableTools()
124
+ } catch (error) {
125
+ ctx.logger?.warn?.('docker: container tool registration failed', error)
126
+ }
127
+ ctx.effect(
128
+ () => ctx.webServer.register({
129
+ kind: 'prefix',
130
+ path: CHANNEL,
131
+ handler: (req, res) => handleRequest(req, res, ctx, service).catch((error) => {
132
+ send(res, 500, `handler failure: ${error instanceof Error ? error.message : String(error)}`, 'text/plain; charset=utf-8')
133
+ }),
134
+ }),
135
+ 'docker: rpc route',
136
+ )
137
+ ctx.effect(() => () => service.dispose(), 'docker: jobs')
138
+ }