@wenbin_wb/dsh-bridge 1.0.2 → 1.0.4
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/README.md +28 -10
- package/client/build.mjs +3 -1
- package/client/client.js +13 -35
- package/client/index.js +12 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
# dsh-bridge
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
> DeepSeek Harness 多通道远程访问插件
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
把你本地的 DeepSeek Harness 无缝延伸到手机、平板、公网、甚至 IM 聊天软件。无论你在哪,都能通过扫码、网页或未来的微信/QQ/飞书,随时调用你的 AI 助手。
|
|
8
|
+
|
|
9
|
+
在 DSH 设置页新增「远程访问」面板,开箱即用地获得:
|
|
10
|
+
|
|
11
|
+
- **局域网访问**:手机扫码,同一 Wi-Fi 直接访问
|
|
12
|
+
- **Cloudflare 隧道**:一键暴露公网地址,随时随地连接
|
|
13
|
+
- **自建隧道**:连接自己的隧道服务器,获得固定域名
|
|
14
|
+
- **IM 集成(规划中)**:微信 / QQ / 飞书 / OpenClaw,直接在聊天软件里呼唤你的 Agent
|
|
6
15
|
|
|
7
|
-

|
|
17
|
+

|
|
18
|
+

|
|
9
19
|

|
|
10
20
|
|
|
11
21
|
## 功能
|
|
@@ -16,10 +26,17 @@
|
|
|
16
26
|
- **安全提示**:URL 和二维码带访问警告,防止误分享
|
|
17
27
|
- **版本检查**:进入面板自动检测是否有新版本
|
|
18
28
|
|
|
19
|
-
##
|
|
29
|
+
## Roadmap
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
除了现有的三种访问方式,正在规划:
|
|
32
|
+
|
|
33
|
+
| 目标 | 说明 | 状态 |
|
|
34
|
+
|------|------|------|
|
|
35
|
+
| **微信** | 在微信里直接与你的 Agent 对话 | 规划中 |
|
|
36
|
+
| **QQ Bot** | 接入 QQ 机器人,群聊/私聊唤起 Agent | 规划中 |
|
|
37
|
+
| **飞书** | 飞书消息/机器人集成,办公场景直接调用 | 规划中 |
|
|
38
|
+
| **OpenClaw** | 与 OpenClaw 生态打通 | 规划中 |
|
|
39
|
+
| **Telegram** | 适合自托管的 IM 渠道 | 规划中 |
|
|
23
40
|
|
|
24
41
|
## 环境要求
|
|
25
42
|
|
|
@@ -39,10 +56,9 @@ dsh --version
|
|
|
39
56
|
如果 `dsh` 命令提示"无法识别/找不到",说明它没有全局安装,先安装 DSH:
|
|
40
57
|
|
|
41
58
|
```bash
|
|
42
|
-
npm install -g @deepseek-ai/dsh
|
|
59
|
+
npm install -g @deepseek-ai/dsh
|
|
43
60
|
```
|
|
44
61
|
|
|
45
|
-
> ⚠️ 与 DSH 版本保持一致:本插件基于 `0.1.0-rc.6` 测试,建议安装相同版本以免契约不匹配。
|
|
46
62
|
> 若没有全局安装的权限,也可以用 `npx` 方式(无需全局安装):
|
|
47
63
|
> ```bash
|
|
48
64
|
> npx --yes @deepseek-ai/dsh plugin --profile web add @wenbin_wb/dsh-bridge
|
|
@@ -80,8 +96,10 @@ dsh plugin --profile web add ./dsh-bridge
|
|
|
80
96
|
|
|
81
97
|
### 自建隧道
|
|
82
98
|
|
|
83
|
-
|
|
84
|
-
|
|
99
|
+
需要一台有公网 IP 的服务器。详细搭建步骤见 [自建隧道教程](docs/custom-tunnel.md)。
|
|
100
|
+
|
|
101
|
+
1. 按教程在服务器上部署隧道服务端
|
|
102
|
+
2. 在「自建隧道」卡片中填写 WebSocket 地址(`wss://...`)和访问令牌
|
|
85
103
|
3. 点「保存配置」后点「开启」
|
|
86
104
|
|
|
87
105
|
配置自动持久化到 `~/.dsh/dsh-bridge/config.json`,重启后无需重新填写。
|
package/client/build.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// dsh-bridge 客户端打包:client/index.js → client/client.js
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { dirname, resolve } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { build } from 'esbuild';
|
|
@@ -7,7 +8,8 @@ import { build } from 'esbuild';
|
|
|
7
8
|
const sourceDir = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const packageRoot = resolve(sourceDir, '..');
|
|
9
10
|
const outputPath = resolve(packageRoot, 'client/client.js');
|
|
10
|
-
|
|
11
|
+
// loaderId 必须与 package.json 的 name 一致(DSH 用它来验证 bundle 注册)
|
|
12
|
+
const loaderId = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8')).name;
|
|
11
13
|
|
|
12
14
|
const result = await build({
|
|
13
15
|
entryPoints: [resolve(sourceDir, 'index.js')],
|
package/client/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({
|
|
2
|
-
id: "dsh-bridge",
|
|
2
|
+
id: "@wenbin_wb/dsh-bridge",
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
@@ -47,6 +47,7 @@ var BRIDGE_ENDPOINTS = {
|
|
|
47
47
|
// client/index.js
|
|
48
48
|
var GITHUB_URL = "https://github.com/wenbin-wb/dsh-bridge";
|
|
49
49
|
var ISSUES_URL = "https://github.com/wenbin-wb/dsh-bridge/issues/new";
|
|
50
|
+
var TUNNEL_DOCS_URL = "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/custom-tunnel.md";
|
|
50
51
|
var name = "dsh-bridge";
|
|
51
52
|
var inject = ["slots", "connection"];
|
|
52
53
|
function semverGt(a, b) {
|
|
@@ -64,17 +65,17 @@ function semverGt(a, b) {
|
|
|
64
65
|
return av.pre > bv.pre;
|
|
65
66
|
}
|
|
66
67
|
var s = {
|
|
67
|
-
card: { background: "var(--dsw-alias-bg-layer-1
|
|
68
|
+
card: { background: "var(--dsw-alias-bg-layer-1,transparent)", border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", borderRadius: 12, padding: "16px 20px", marginBottom: 16 },
|
|
68
69
|
block: { borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", marginTop: 12, paddingTop: 12 },
|
|
69
70
|
muted: { color: "var(--dsw-alias-label-tertiary,#8b93a1)", fontSize: 12, lineHeight: 1.5 },
|
|
70
|
-
label: { color: "var(--dsw-alias-label-primary,
|
|
71
|
-
code: { fontFamily: "ui-monospace,Menlo,monospace", fontSize: 12, wordBreak: "break-all", color: "var(--dsw-alias-label-primary,
|
|
71
|
+
label: { color: "var(--dsw-alias-label-primary,currentColor)", fontSize: 13, fontWeight: 500 },
|
|
72
|
+
code: { fontFamily: "ui-monospace,Menlo,monospace", fontSize: 12, wordBreak: "break-all", color: "var(--dsw-alias-label-primary,currentColor)" },
|
|
72
73
|
btnPri: { font: "inherit", cursor: "pointer", border: "none", background: "var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary,#4f6ef7))", color: "#fff", height: 32, padding: "0 14px", borderRadius: 999, fontSize: 13, fontWeight: 500, display: "inline-flex", alignItems: "center", gap: 4 },
|
|
73
|
-
btnGhost: { font: "inherit", cursor: "pointer", border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", background: "var(--dsw-alias-bg-layer-1
|
|
74
|
+
btnGhost: { font: "inherit", cursor: "pointer", border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", background: "var(--dsw-alias-bg-layer-1,transparent)", color: "var(--dsw-alias-label-primary,currentColor)", height: 32, padding: "0 14px", borderRadius: 999, fontSize: 13, display: "inline-flex", alignItems: "center", gap: 4, textDecoration: "none" },
|
|
74
75
|
btnLink: { font: "inherit", cursor: "pointer", border: "none", background: "none", color: "var(--dsw-alias-brand-primary,#4f6ef7)", fontSize: 12, padding: 0, display: "inline-flex", alignItems: "center", gap: 3, textDecoration: "none" },
|
|
75
76
|
qr: { width: 200, height: 200, borderRadius: 10, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", margin: "8px 0", display: "block" },
|
|
76
77
|
tag: { display: "inline-flex", alignItems: "center", padding: "2px 8px", borderRadius: 999, fontSize: 12, fontWeight: 500 },
|
|
77
|
-
input: { width: "100%", font: "inherit", fontSize: 13, padding: "7px 10px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", background: "var(--dsw-alias-bg-layer-1
|
|
78
|
+
input: { width: "100%", font: "inherit", fontSize: 13, padding: "7px 10px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", background: "var(--dsw-alias-bg-layer-1,transparent)", color: "var(--dsw-alias-label-primary,currentColor)", outline: "none", boxSizing: "border-box" },
|
|
78
79
|
warn: { background: "var(--dsw-alias-state-warn-bg,#fffbeb)", border: "1px solid var(--dsw-alias-state-warn-border,#fde68a)", borderRadius: 8, padding: "10px 14px", fontSize: 12, color: "var(--dsw-alias-state-warn-primary,#92400e)", lineHeight: 1.6 },
|
|
79
80
|
tip: { background: "var(--dsw-alias-bg-layer-2,#f9fafb)", borderRadius: 8, padding: "10px 14px", fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", lineHeight: 1.6 }
|
|
80
81
|
};
|
|
@@ -137,38 +138,15 @@ function QrBlock({ url, qr, onReset }) {
|
|
|
137
138
|
);
|
|
138
139
|
}
|
|
139
140
|
var CustomTunnelGuide = React.memo(function CustomTunnelGuide2() {
|
|
140
|
-
const [open, setOpen] = React.useState(false);
|
|
141
|
-
const toggle = React.useCallback(() => setOpen((v) => !v), []);
|
|
142
141
|
return React.createElement(
|
|
143
142
|
"div",
|
|
144
143
|
{ style: s.block },
|
|
145
|
-
React.createElement("
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{ style: s.tip },
|
|
152
|
-
React.createElement("div", { style: { fontWeight: 500, marginBottom: 6, color: "var(--dsw-alias-label-primary,inherit)" } }, "\u642D\u5EFA\u6B65\u9AA4"),
|
|
153
|
-
React.createElement(
|
|
154
|
-
"ol",
|
|
155
|
-
{ style: { margin: 0, paddingLeft: 18, display: "flex", flexDirection: "column", gap: 4 } },
|
|
156
|
-
React.createElement("li", null, "\u5728\u516C\u7F51\u670D\u52A1\u5668\u4E0A\u5B89\u88C5 Node.js 18+"),
|
|
157
|
-
React.createElement("li", null, "\u90E8\u7F72\u96A7\u9053\u670D\u52A1\u7AEF\uFF0C\u63A8\u8350\u4F7F\u7528 frp \u6216\u517C\u5BB9 WebSocket \u7684\u53CD\u5411\u4EE3\u7406"),
|
|
158
|
-
React.createElement("li", null, "\u8BB0\u5F55\u670D\u52A1\u5668\u7684\u516C\u7F51\u57DF\u540D\uFF08\u5982 tunnel.example.com\uFF09\u548C\u8BBF\u95EE\u4EE4\u724C"),
|
|
159
|
-
React.createElement("li", null, "\u5728\u4E0B\u65B9\u586B\u5199 WebSocket \u5730\u5740\uFF08wss://...\uFF09\u548C\u4EE4\u724C\uFF0C\u4FDD\u5B58\u540E\u70B9\u5F00\u542F")
|
|
160
|
-
),
|
|
161
|
-
React.createElement(
|
|
162
|
-
"div",
|
|
163
|
-
{ style: { marginTop: 8, paddingTop: 8, borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)" } },
|
|
164
|
-
React.createElement("div", { style: { fontWeight: 500, marginBottom: 4, color: "var(--dsw-alias-label-primary,inherit)" } }, "\u5730\u5740\u683C\u5F0F"),
|
|
165
|
-
React.createElement(
|
|
166
|
-
"code",
|
|
167
|
-
{ style: { ...s.code, display: "block", padding: "6px 8px", background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", borderRadius: 6 } },
|
|
168
|
-
"wss://tunnel.example.com/connect"
|
|
169
|
-
)
|
|
170
|
-
)
|
|
171
|
-
)
|
|
144
|
+
React.createElement("a", {
|
|
145
|
+
href: TUNNEL_DOCS_URL,
|
|
146
|
+
target: "_blank",
|
|
147
|
+
rel: "noreferrer",
|
|
148
|
+
style: { ...s.btnGhost, fontSize: 12, height: 28, display: "inline-flex" }
|
|
149
|
+
}, "\u67E5\u770B\u81EA\u5EFA\u96A7\u9053\u670D\u52A1\u5668\u642D\u5EFA\u6559\u7A0B")
|
|
172
150
|
);
|
|
173
151
|
});
|
|
174
152
|
var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serverUrl: initUrl, accessToken: initToken, onSave }) {
|
package/client/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { BRIDGE_RPC_CHANNEL, BRIDGE_ENDPOINTS } from '../lib/bridge-rpc.js';
|
|
|
4
4
|
|
|
5
5
|
const GITHUB_URL = 'https://github.com/wenbin-wb/dsh-bridge';
|
|
6
6
|
const ISSUES_URL = 'https://github.com/wenbin-wb/dsh-bridge/issues/new';
|
|
7
|
+
const TUNNEL_DOCS_URL = 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/custom-tunnel.md';
|
|
7
8
|
|
|
8
9
|
const name = 'dsh-bridge';
|
|
9
10
|
const inject = ['slots', 'connection'];
|
|
@@ -25,17 +26,17 @@ function semverGt(a, b) {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const s = {
|
|
28
|
-
card: { background: 'var(--dsw-alias-bg-layer-1
|
|
29
|
+
card: { background: 'var(--dsw-alias-bg-layer-1,transparent)', border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', borderRadius: 12, padding: '16px 20px', marginBottom: 16 },
|
|
29
30
|
block: { borderTop: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', marginTop: 12, paddingTop: 12 },
|
|
30
31
|
muted: { color: 'var(--dsw-alias-label-tertiary,#8b93a1)', fontSize: 12, lineHeight: 1.5 },
|
|
31
|
-
label: { color: 'var(--dsw-alias-label-primary,
|
|
32
|
-
code: { fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 12, wordBreak: 'break-all', color: 'var(--dsw-alias-label-primary,
|
|
32
|
+
label: { color: 'var(--dsw-alias-label-primary,currentColor)', fontSize: 13, fontWeight: 500 },
|
|
33
|
+
code: { fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 12, wordBreak: 'break-all', color: 'var(--dsw-alias-label-primary,currentColor)' },
|
|
33
34
|
btnPri: { font: 'inherit', cursor: 'pointer', border: 'none', background: 'var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary,#4f6ef7))', color: '#fff', height: 32, padding: '0 14px', borderRadius: 999, fontSize: 13, fontWeight: 500, display: 'inline-flex', alignItems: 'center', gap: 4 },
|
|
34
|
-
btnGhost: { font: 'inherit', cursor: 'pointer', border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-1
|
|
35
|
+
btnGhost: { font: 'inherit', cursor: 'pointer', border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-1,transparent)', color: 'var(--dsw-alias-label-primary,currentColor)', height: 32, padding: '0 14px', borderRadius: 999, fontSize: 13, display: 'inline-flex', alignItems: 'center', gap: 4, textDecoration: 'none' },
|
|
35
36
|
btnLink: { font: 'inherit', cursor: 'pointer', border: 'none', background: 'none', color: 'var(--dsw-alias-brand-primary,#4f6ef7)', fontSize: 12, padding: 0, display: 'inline-flex', alignItems: 'center', gap: 3, textDecoration: 'none' },
|
|
36
37
|
qr: { width: 200, height: 200, borderRadius: 10, border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', margin: '8px 0', display: 'block' },
|
|
37
38
|
tag: { display: 'inline-flex', alignItems: 'center', padding: '2px 8px', borderRadius: 999, fontSize: 12, fontWeight: 500 },
|
|
38
|
-
input: { width: '100%', font: 'inherit', fontSize: 13, padding: '7px 10px', borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-1
|
|
39
|
+
input: { width: '100%', font: 'inherit', fontSize: 13, padding: '7px 10px', borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-1,transparent)', color: 'var(--dsw-alias-label-primary,currentColor)', outline: 'none', boxSizing: 'border-box' },
|
|
39
40
|
warn: { background: 'var(--dsw-alias-state-warn-bg,#fffbeb)', border: '1px solid var(--dsw-alias-state-warn-border,#fde68a)', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: 'var(--dsw-alias-state-warn-primary,#92400e)', lineHeight: 1.6 },
|
|
40
41
|
tip: { background: 'var(--dsw-alias-bg-layer-2,#f9fafb)', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', lineHeight: 1.6 },
|
|
41
42
|
};
|
|
@@ -95,28 +96,13 @@ function QrBlock({ url, qr, onReset }) {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
const CustomTunnelGuide = React.memo(function CustomTunnelGuide() {
|
|
98
|
-
const [open, setOpen] = React.useState(false);
|
|
99
|
-
const toggle = React.useCallback(() => setOpen(v => !v), []);
|
|
100
99
|
return React.createElement('div', { style: s.block },
|
|
101
|
-
React.createElement('
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
React.createElement('ol', { style: { margin: 0, paddingLeft: 18, display: 'flex', flexDirection: 'column', gap: 4 } },
|
|
108
|
-
React.createElement('li', null, '在公网服务器上安装 Node.js 18+'),
|
|
109
|
-
React.createElement('li', null, '部署隧道服务端,推荐使用 frp 或兼容 WebSocket 的反向代理'),
|
|
110
|
-
React.createElement('li', null, '记录服务器的公网域名(如 tunnel.example.com)和访问令牌'),
|
|
111
|
-
React.createElement('li', null, '在下方填写 WebSocket 地址(wss://...)和令牌,保存后点开启'),
|
|
112
|
-
),
|
|
113
|
-
React.createElement('div', { style: { marginTop: 8, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2,#e5e7eb)' } },
|
|
114
|
-
React.createElement('div', { style: { fontWeight: 500, marginBottom: 4, color: 'var(--dsw-alias-label-primary,inherit)' } }, '地址格式'),
|
|
115
|
-
React.createElement('code', { style: { ...s.code, display: 'block', padding: '6px 8px', background: 'var(--dsw-alias-bg-layer-2,#f3f4f6)', borderRadius: 6 } },
|
|
116
|
-
'wss://tunnel.example.com/connect'
|
|
117
|
-
),
|
|
118
|
-
),
|
|
119
|
-
),
|
|
100
|
+
React.createElement('a', {
|
|
101
|
+
href: TUNNEL_DOCS_URL,
|
|
102
|
+
target: '_blank',
|
|
103
|
+
rel: 'noreferrer',
|
|
104
|
+
style: { ...s.btnGhost, fontSize: 12, height: 28, display: 'inline-flex' },
|
|
105
|
+
}, '查看自建隧道服务器搭建教程'),
|
|
120
106
|
);
|
|
121
107
|
});
|
|
122
108
|
|