@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 CHANGED
@@ -1,11 +1,21 @@
1
1
  # dsh-bridge
2
2
 
3
+ ![dsh-bridge banner](docs/banner.jpg)
4
+
3
5
  > DeepSeek Harness 多通道远程访问插件
4
6
 
5
- DSH 设置页新增「远程访问」面板,支持局域网二维码、Cloudflare 隧道、自建 WebSocket 隧道三种方式从任意设备访问你的 DSH 实例。
7
+ 把你本地的 DeepSeek Harness 无缝延伸到手机、平板、公网、甚至 IM 聊天软件。无论你在哪,都能通过扫码、网页或未来的微信/QQ/飞书,随时调用你的 AI 助手。
8
+
9
+ 在 DSH 设置页新增「远程访问」面板,开箱即用地获得:
10
+
11
+ - **局域网访问**:手机扫码,同一 Wi-Fi 直接访问
12
+ - **Cloudflare 隧道**:一键暴露公网地址,随时随地连接
13
+ - **自建隧道**:连接自己的隧道服务器,获得固定域名
14
+ - **IM 集成(规划中)**:微信 / QQ / 飞书 / OpenClaw,直接在聊天软件里呼唤你的 Agent
6
15
 
7
- ![DSH Plugin](https://img.shields.io/badge/dsh--plugin-0.1.0--rc.6-blue)
8
- ![License](https://img.shields.io/badge/license-MIT-green)
16
+ ![npm](https://img.shields.io/npm/v/@wenbin_wb/dsh-bridge?label=npm)
17
+ ![npm downloads](https://img.shields.io/npm/dt/@wenbin_wb/dsh-bridge?label=downloads)
18
+ ![npm license](https://img.shields.io/npm/l/@wenbin_wb/dsh-bridge?label=license)
9
19
  ![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)
10
20
 
11
21
  ## 功能
@@ -16,10 +26,17 @@
16
26
  - **安全提示**:URL 和二维码带访问警告,防止误分享
17
27
  - **版本检查**:进入面板自动检测是否有新版本
18
28
 
19
- ## 兼容性
29
+ ## Roadmap
20
30
 
21
- > 本插件基于 **DeepSeek Harness v0.1.0-rc.6** 开发测试。
22
- > DSH 是开发者预览版,插件契约可能随版本变化,请关注 [CHANGELOG](./CHANGELOG.md)。
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@0.1.0-rc.6
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
- 1. 展开「如何搭建自建隧道服务器?」查看搭建步骤
84
- 2. 填写 WebSocket 服务器地址(`wss://...`)和访问令牌
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
- const loaderId = 'dsh-bridge';
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,#fff)", border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", borderRadius: 12, padding: "16px 20px", marginBottom: 16 },
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,inherit)", fontSize: 13, fontWeight: 500 },
71
- code: { fontFamily: "ui-monospace,Menlo,monospace", fontSize: 12, wordBreak: "break-all", color: "var(--dsw-alias-label-primary,inherit)" },
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,#fff)", color: "var(--dsw-alias-label-primary,inherit)", height: 32, padding: "0 14px", borderRadius: 999, fontSize: 13, display: "inline-flex", alignItems: "center", gap: 4 },
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,#fff)", color: "var(--dsw-alias-label-primary,inherit)", outline: "none", boxSizing: "border-box" },
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("button", {
146
- style: { ...s.btnGhost, fontSize: 12, height: 28, marginBottom: open ? 10 : 0 },
147
- onClick: toggle
148
- }, open ? "\u25B2 \u6536\u8D77\u642D\u5EFA\u6559\u7A0B" : "\u25B6 \u5982\u4F55\u642D\u5EFA\u81EA\u5EFA\u96A7\u9053\u670D\u52A1\u5668\uFF1F"),
149
- open && React.createElement(
150
- "div",
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,#fff)', border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', borderRadius: 12, padding: '16px 20px', marginBottom: 16 },
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,inherit)', fontSize: 13, fontWeight: 500 },
32
- code: { fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 12, wordBreak: 'break-all', color: 'var(--dsw-alias-label-primary,inherit)' },
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,#fff)', color: 'var(--dsw-alias-label-primary,inherit)', height: 32, padding: '0 14px', borderRadius: 999, fontSize: 13, display: 'inline-flex', alignItems: 'center', gap: 4 },
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,#fff)', color: 'var(--dsw-alias-label-primary,inherit)', outline: 'none', boxSizing: 'border-box' },
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('button', {
102
- style: { ...s.btnGhost, fontSize: 12, height: 28, marginBottom: open ? 10 : 0 },
103
- onClick: toggle,
104
- }, open ? '▲ 收起搭建教程' : '▶ 如何搭建自建隧道服务器?'),
105
- open && React.createElement('div', { style: s.tip },
106
- React.createElement('div', { style: { fontWeight: 500, marginBottom: 6, color: 'var(--dsw-alias-label-primary,inherit)' } }, '搭建步骤'),
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenbin_wb/dsh-bridge",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "DSH 多渠道接入桥:局域网二维码、自建隧道、机器人集成",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",