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.
- package/LICENSE +21 -0
- package/README.md +80 -2
- package/cordis.patch.yml +3 -0
- package/docs/ai-chat.png +0 -0
- package/docs/ai-new-session.png +0 -0
- package/docs/apple-container.png +0 -0
- package/docs/attach.png +0 -0
- package/docs/docker-console.png +0 -0
- package/docs/docker-overview.png +0 -0
- package/docs/docker-volumes.png +0 -0
- package/docs/image-architecture.png +0 -0
- package/docs/package-install.png +0 -0
- package/docs/provider-switch.png +0 -0
- package/docs/run-container.png +0 -0
- package/index.js +138 -0
- package/lib/client.js +3560 -0
- package/package.json +62 -4
- package/src/client/api.js +98 -0
- package/src/client/attach.jsx +134 -0
- package/src/client/components.jsx +320 -0
- package/src/client/hooks.js +161 -0
- package/src/client/i18n.js +502 -0
- package/src/client/icons.jsx +227 -0
- package/src/client/index.jsx +76 -0
- package/src/client/page.jsx +782 -0
- package/src/client/run-container.jsx +187 -0
- package/src/client/runs.js +182 -0
- package/src/client/store.js +91 -0
- package/src/client/styles.js +371 -0
- package/src/client/tabs/compose.jsx +94 -0
- package/src/client/tabs/console.jsx +211 -0
- package/src/client/tabs/containers.jsx +148 -0
- package/src/client/tabs/images.jsx +127 -0
- package/src/client/tabs/networks.jsx +91 -0
- package/src/client/tabs/overview.jsx +266 -0
- package/src/client/tabs/volumes.jsx +76 -0
- package/src/host/apple-cli.js +307 -0
- package/src/host/apple.js +471 -0
- package/src/host/attach.js +246 -0
- package/src/host/commands.js +235 -0
- package/src/host/docker-cli.js +405 -0
- package/src/host/jobs.js +245 -0
- package/src/host/parsers.js +484 -0
- package/src/host/runtime.js +285 -0
- package/src/host/service.js +836 -0
- package/src/host/tools.js +198 -0
- 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
|
-
#
|
|
1
|
+
# DSH 容器
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DSH 左栏容器面板:管理 macOS 上的 Docker(colima / Docker Desktop / OrbStack / remote context)和 Apple `container`。
|
|
4
|
+
|
|
5
|
+

|
|
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
|
+
```
|
package/cordis.patch.yml
ADDED
package/docs/ai-chat.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/attach.png
ADDED
|
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
|
+
}
|