dsh-vpn 0.1.3 → 0.1.5
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 +30 -11
- package/assets/vpn-ops.md +11 -7
- package/lib/client.js +16 -3
- package/lib/index.js +55 -11
- package/lib/skill.js +1 -1
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# dsh-vpn
|
|
2
2
|
|
|
3
|
-
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
3
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件:在**你自己的 VPS** 上一键部署加密的 VPN 服务,保护你的网络连接安全。在 dsh 里填好 VPS 的 IP、端口、用户名和密码,一键部署 sing-box,然后扫二维码导入手机或电脑上的客户端。
|
|
4
|
+
|
|
5
|
+
## 用途:保护你的网络安全
|
|
6
|
+
|
|
7
|
+
- **加密你的网络流量。** 在公共 Wi-Fi(咖啡馆、机场、酒店)等不可信网络上,你设备和服务器之间的全部流量都经过加密,别人无法窃听或篡改。
|
|
8
|
+
- **保护隐私。** 同一网络上的其他人和网络运营方看不到你访问的内容。
|
|
9
|
+
- **服务器由你自己掌控。** 服务部署在你自己的 VPS 上,不经过任何第三方 VPN 服务商,数据只在你的设备和你的服务器之间传输。
|
|
10
|
+
- **默认安全的配置。** 协议之间的密钥各自独立、随机生成;推荐的 IP 证书让客户端正常校验服务器身份,防止中间人攻击。
|
|
11
|
+
|
|
12
|
+
请在遵守所在地法律法规和服务器提供商服务条款的前提下使用。
|
|
13
|
+
|
|
14
|
+
## 特性
|
|
4
15
|
|
|
5
16
|
- **不依赖本机的 ssh 命令**:内置纯 JS 的 SSH 客户端([ssh2](https://github.com/mscdex/ssh2)),用密码登录。
|
|
6
17
|
- **两种用法**:在设置页点按钮,或者直接对 agent 说“帮我装个节点”。
|
|
@@ -27,6 +38,14 @@ dsh plugin --profile web add dsh-vpn
|
|
|
27
38
|
3. 在「节点二维码」里点「刷新二维码」,用手机或电脑上的客户端扫码导入。
|
|
28
39
|
4. 服务器防火墙的端口由插件自动放行,不用手动操作;安装完成后提示框会列出放行了哪些端口。
|
|
29
40
|
|
|
41
|
+
## 语言
|
|
42
|
+
|
|
43
|
+
整个插件的语言跟随 dsh 的界面语言(「设置 → 通用设置 → 语言」),目前支持 dsh 提供的中文和 English:
|
|
44
|
+
|
|
45
|
+
- 设置页切换语言后立即生效。
|
|
46
|
+
- agent 安装、卸载前的审批提示,以及 agent 收到的提示,也使用同一种语言。浏览器端会把 dsh 当前的界面语言告诉插件,插件把它保存下来,dsh 重启后仍然沿用。
|
|
47
|
+
- 只有从来没有打开过 dsh 网页界面时(例如纯命令行运行),才临时使用电脑的系统语言。
|
|
48
|
+
|
|
30
49
|
## 防火墙
|
|
31
50
|
|
|
32
51
|
安装时插件会检测 VPS 上启用的防火墙,自动放行所选协议的端口(使用 IP 证书时还有 TCP 80):
|
|
@@ -65,7 +84,7 @@ IP 证书由 sing-box 1.14 内置的 ACME 向 Let's Encrypt 申请([2026 年
|
|
|
65
84
|
| 接口鉴权 | 设置页的接口挂在 dsh 自己的 `/api` 下,复用 dsh 的登录 cookie 和防跨站、防 DNS rebinding 检查 |
|
|
66
85
|
| 主机指纹 | 首次连接时记录 SSH 主机指纹,之后指纹变化就拒绝连接,防止中间人截获密码 |
|
|
67
86
|
| sudo | 非 root 用户用 `sudo -S`:密码只经 stdin 传入,不会出现在命令行,也不会传给脚本 |
|
|
68
|
-
| agent 操作 | agent
|
|
87
|
+
| agent 操作 | agent 安装或卸载前会弹出审批,写明目标服务器、协议,以及是否重新生成密钥 |
|
|
69
88
|
| 证书 | 推荐用 IP 证书,所有协议都正常校验;自签模式下 Hysteria2 锁定指纹,TUIC 和 AnyTLS 会标注“需跳过证书校验” |
|
|
70
89
|
|
|
71
90
|
## 组成
|
|
@@ -101,20 +120,12 @@ DSH_SRC=/path/to/deepseek-harness pnpm typecheck # 对照 dsh 源码做宿主
|
|
|
101
120
|
|
|
102
121
|
npm 上的 `@deepseek-ai/*` 包版本过旧,所以 `build` 不做类型检查,类型检查对照 dsh 源码单独进行。
|
|
103
122
|
|
|
104
|
-
## 发布
|
|
105
|
-
|
|
106
|
-
推送与 `package.json` 版本号一致的标签,GitHub Actions 会先跑测试,再发布到 npm。需要在仓库 Secrets 里配置 `NPM_TOKEN`。仓库是私有的,npm 不支持为私有仓库生成来源证明(provenance),所以发布时不带来源证明:
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
git tag v0.1.0 && git push origin v0.1.0
|
|
110
|
-
```
|
|
111
|
-
|
|
112
123
|
端到端测试会在目标机器上**安装再卸载**,并用 sing-box 客户端实测每种协议的连通性。本地可以用 [Lima](https://lima-vm.io) 虚拟机:
|
|
113
124
|
|
|
114
125
|
```bash
|
|
115
126
|
limactl start --name=vps --vm-type=vz --network=vzNAT --set '.mounts=[]' template:ubuntu-24.04
|
|
116
127
|
# 在虚拟机里开启 sshd 的密码登录并设置密码,然后:
|
|
117
|
-
DSH_VPN_E2E_HOST
|
|
128
|
+
DSH_VPN_E2E_HOST=<测试虚拟机 IP> DSH_VPN_E2E_USER=root DSH_VPN_E2E_PASSWORD=… pnpm test:e2e
|
|
118
129
|
```
|
|
119
130
|
|
|
120
131
|
防火墙测试会依次启用 ufw、firewalld、iptables、nftables,**只能在测试虚拟机上运行**:
|
|
@@ -132,6 +143,14 @@ DSH_VPN_E2E_ACME=https://localhost:14000/dir DSH_VPN_E2E_HOST=… pnpm test:e2e
|
|
|
132
143
|
|
|
133
144
|
在插件配置里设置 `acmeProvider`,就可以让插件改用 Pebble 或其他 ACME 服务器。
|
|
134
145
|
|
|
146
|
+
## 发布
|
|
147
|
+
|
|
148
|
+
推送与 `package.json` 版本号一致的标签,GitHub Actions 会先跑测试,再发布到 npm。需要在仓库 Secrets 里配置 `NPM_TOKEN`。仓库是私有的,npm 不支持为私有仓库生成来源证明(provenance),所以发布时不带来源证明:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
152
|
+
```
|
|
153
|
+
|
|
135
154
|
## TODO
|
|
136
155
|
|
|
137
156
|
- [ ] SSH 私钥登录
|
package/assets/vpn-ops.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# vpn-ops
|
|
2
2
|
|
|
3
|
-
Deploy and operate a personal sing-box
|
|
3
|
+
Deploy and operate a personal, encrypted VPN (sing-box) on the user's own VPS. The plugin logs in over SSH with the password saved in dsh Settings (bottom-left gear) → VPN 设置. No local ssh client is needed.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This is a security and privacy tool. It encrypts all traffic between the user's devices and a server they control, so connections stay private on untrusted networks such as public Wi-Fi, without relying on a third-party VPN provider. Describe it in those terms, and remind the user to use it in line with local laws and their server provider's terms of service.
|
|
4
8
|
|
|
5
9
|
## Workflow
|
|
6
10
|
|
|
@@ -19,11 +23,11 @@ The settings page offers **self-signed** (default) or **IP certificate** (recomm
|
|
|
19
23
|
|
|
20
24
|
| id | Name | Transport | Notes |
|
|
21
25
|
|---|---|---|---|
|
|
22
|
-
| `reality` | VLESS Reality | TCP | Default.
|
|
26
|
+
| `reality` | VLESS Reality | TCP | Default. Strong encryption; the handshake looks like ordinary HTTPS to a well-known website, which keeps usage private. |
|
|
23
27
|
| `hy2` | Hysteria2 | UDP | Default. Fast on lossy links; the link pins the certificate. |
|
|
24
28
|
| `tuic` | TUIC v5 | UDP | With a self-signed cert, clients skip verification; with an IP certificate, they verify normally. |
|
|
25
29
|
| `anytls` | AnyTLS | TCP | Same as TUIC. |
|
|
26
|
-
| `ss` | Shadowsocks 2022 | TCP+UDP | Widely supported
|
|
30
|
+
| `ss` | Shadowsocks 2022 | TCP+UDP | Widely supported, modern AEAD encryption, no TLS layer. |
|
|
27
31
|
|
|
28
32
|
## Troubleshooting
|
|
29
33
|
|
|
@@ -31,7 +35,7 @@ The settings page offers **self-signed** (default) or **IP certificate** (recomm
|
|
|
31
35
|
|---|---|---|
|
|
32
36
|
| `auth_failed` | Wrong username or password | User fixes it on the settings page |
|
|
33
37
|
| `host_key_mismatch` | Server reinstalled or replaced, or a man-in-the-middle | Ask whether the server was reinstalled. Only then may the user click 重置指纹 (Reset fingerprint) |
|
|
34
|
-
| `connect_failed` / `timeout` | Wrong IP or port, SSH
|
|
38
|
+
| `connect_failed` / `timeout` | Wrong IP or port, SSH not reachable, server down | Check the IP and SSH port on the settings page |
|
|
35
39
|
| `sudo_failed` | User is not root and lacks sudo, or the sudo password differs | Use root, or grant sudo |
|
|
36
40
|
| `no_systemd` | Container or OpenRC VPS | Not supported yet |
|
|
37
41
|
| `download_failed` | VPS cannot reach GitHub | Retry later |
|
|
@@ -39,8 +43,8 @@ The settings page offers **self-signed** (default) or **IP certificate** (recomm
|
|
|
39
43
|
| `port80_in_use` | Another program (nginx, …) holds port 80 | Stop it, or use self-signed |
|
|
40
44
|
| Status says the certificate expires within 24 h or has expired | Renewal failing | Open TCP 80; an expired certificate breaks every TLS protocol |
|
|
41
45
|
| Installed and active, but the client times out | Cloud security group (the VPS firewall is handled automatically) | Open the ports from `result.firewall.opened` in the cloud console |
|
|
42
|
-
| Reality fails, others work |
|
|
43
|
-
| UDP protocols fail, TCP works |
|
|
44
|
-
| Everything stopped after working | IP
|
|
46
|
+
| Reality fails, others work | The Reality handshake site (SNI) is unreachable from the server | Reinstall with another SNI (future option) |
|
|
47
|
+
| UDP protocols fail, TCP works | The current network drops or rate-limits UDP (common on some Wi-Fi and mobile networks) | Use Reality or AnyTLS |
|
|
48
|
+
| Everything stopped after working | The server IP or its ports are no longer reachable from the user's network | `vpn_install` with `regenerate: true` for new ports; if it is still unreachable, check with the server provider |
|
|
45
49
|
|
|
46
50
|
Never paste passwords, private keys or the state file into chat. Share links are secrets too: show them only to the user.
|
package/lib/client.js
CHANGED
|
@@ -55,7 +55,9 @@ var vpnApi = {
|
|
|
55
55
|
status: () => post("/status"),
|
|
56
56
|
links: () => post("/links"),
|
|
57
57
|
install: (regenerate) => post("/install", { regenerate }),
|
|
58
|
-
uninstall: () => post("/uninstall")
|
|
58
|
+
uninstall: () => post("/uninstall"),
|
|
59
|
+
/** Tell the host which language dsh's UI shows (approval prompts follow it). */
|
|
60
|
+
reportUiLocale: (locale) => post("/ui-locale", { locale })
|
|
59
61
|
};
|
|
60
62
|
|
|
61
63
|
// src/client/locales.ts
|
|
@@ -63,7 +65,7 @@ var NS = "dsh-vpn";
|
|
|
63
65
|
var zh = {
|
|
64
66
|
title: "VPN \u8282\u70B9",
|
|
65
67
|
nav: "VPN \u8BBE\u7F6E",
|
|
66
|
-
description: "\u5728\u4F60\u7684 VPS \u4E0A\u4E00\u952E\u90E8\u7F72
|
|
68
|
+
description: "\u5728\u4F60\u81EA\u5DF1\u7684 VPS \u4E0A\u4E00\u952E\u90E8\u7F72\u52A0\u5BC6 VPN\uFF0C\u4FDD\u62A4\u4F60\u5728\u516C\u5171 Wi-Fi \u7B49\u7F51\u7EDC\u4E2D\u7684\u8FDE\u63A5\u5B89\u5168\u548C\u9690\u79C1\uFF1B\u626B\u7801\u5373\u53EF\u5BFC\u5165\u624B\u673A\u6216\u7535\u8111\u4E0A\u7684\u5BA2\u6237\u7AEF\u3002",
|
|
67
69
|
serverSection: "\u670D\u52A1\u5668",
|
|
68
70
|
host: "\u670D\u52A1\u5668 IP \u6216\u57DF\u540D",
|
|
69
71
|
hostHint: "\u4F60\u8D2D\u4E70\u7684 VPS \u7684\u516C\u7F51\u5730\u5740\u3002",
|
|
@@ -154,7 +156,7 @@ var zh = {
|
|
|
154
156
|
var en = {
|
|
155
157
|
title: "VPN node",
|
|
156
158
|
nav: "VPN",
|
|
157
|
-
description: "Deploy
|
|
159
|
+
description: "Deploy an encrypted VPN to your own VPS in one click to keep your connections private and secure on networks like public Wi-Fi; import it into client apps by scanning QR codes.",
|
|
158
160
|
serverSection: "Server",
|
|
159
161
|
host: "Server IP or domain",
|
|
160
162
|
hostHint: "Public address of your VPS.",
|
|
@@ -1400,6 +1402,17 @@ function apply(ctx) {
|
|
|
1400
1402
|
style.remove();
|
|
1401
1403
|
};
|
|
1402
1404
|
}, "dsh-vpn: styles");
|
|
1405
|
+
const reportLocale = (locale, attempt = 0) => {
|
|
1406
|
+
void vpnApi.reportUiLocale(locale).then((r) => {
|
|
1407
|
+
if (!r.ok && attempt < 3) setTimeout(() => {
|
|
1408
|
+
reportLocale(locale, attempt + 1);
|
|
1409
|
+
}, 2e3 * (attempt + 1));
|
|
1410
|
+
});
|
|
1411
|
+
};
|
|
1412
|
+
reportLocale(ctx.locale.getSnapshot().active);
|
|
1413
|
+
ctx.on("locale/change", (snapshot) => {
|
|
1414
|
+
reportLocale(snapshot.active);
|
|
1415
|
+
});
|
|
1403
1416
|
const t = ctx.locale.bind(NS);
|
|
1404
1417
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1405
1418
|
name: "settings.section",
|
package/lib/index.js
CHANGED
|
@@ -20264,8 +20264,13 @@ function json(status, value) {
|
|
|
20264
20264
|
headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" }
|
|
20265
20265
|
});
|
|
20266
20266
|
}
|
|
20267
|
-
function apiHandlers(service) {
|
|
20267
|
+
function apiHandlers(service, uiLang, onUiLang) {
|
|
20268
20268
|
return {
|
|
20269
|
+
"/ui-locale": async (body) => {
|
|
20270
|
+
if (typeof body.locale !== "string" || body.locale.length > 35) return { ok: false, error: "bad_request", message: "locale must be a string" };
|
|
20271
|
+
if (uiLang?.set(body.locale)) onUiLang?.();
|
|
20272
|
+
return { ok: true, lang: uiLang?.get() };
|
|
20273
|
+
},
|
|
20269
20274
|
"/settings/get": async () => ({ ok: true, settings: publicView(await service.store.get()) }),
|
|
20270
20275
|
"/settings/save": async (body) => {
|
|
20271
20276
|
let saved;
|
|
@@ -20305,8 +20310,8 @@ async function handle(handler, request) {
|
|
|
20305
20310
|
return json(500, { ok: false, error: "internal", message: err instanceof Error ? err.message : String(err) });
|
|
20306
20311
|
}
|
|
20307
20312
|
}
|
|
20308
|
-
function registerApi(ctx, service) {
|
|
20309
|
-
for (const [path, handler] of Object.entries(apiHandlers(service))) {
|
|
20313
|
+
function registerApi(ctx, service, uiLang, onUiLang) {
|
|
20314
|
+
for (const [path, handler] of Object.entries(apiHandlers(service, uiLang, onUiLang))) {
|
|
20310
20315
|
ctx.effect(() => ctx.connection.fetch.register({
|
|
20311
20316
|
path: `${API_BASE}${path}`,
|
|
20312
20317
|
methods: ["POST"],
|
|
@@ -20672,6 +20677,15 @@ var CredentialSettingsStore = class {
|
|
|
20672
20677
|
});
|
|
20673
20678
|
}
|
|
20674
20679
|
};
|
|
20680
|
+
var UI_LANG_KEY = credentialKey("dsh-vpn", "ui-locale");
|
|
20681
|
+
async function readUiLang(ctx) {
|
|
20682
|
+
const record = await ctx.credentials.readRecord(UI_LANG_KEY);
|
|
20683
|
+
const lang = record?.kind === "grant" ? record.payload?.lang : void 0;
|
|
20684
|
+
return lang === "zh" || lang === "en" ? lang : void 0;
|
|
20685
|
+
}
|
|
20686
|
+
async function writeUiLang(ctx, lang) {
|
|
20687
|
+
await ctx.credentials.modifyRecord(UI_LANG_KEY, async () => ({ kind: "grant", payload: { lang } }));
|
|
20688
|
+
}
|
|
20675
20689
|
|
|
20676
20690
|
// src/i18n.ts
|
|
20677
20691
|
import { execFileSync } from "node:child_process";
|
|
@@ -20696,6 +20710,26 @@ var cached;
|
|
|
20696
20710
|
function osLang() {
|
|
20697
20711
|
return cached ??= detectOsLang();
|
|
20698
20712
|
}
|
|
20713
|
+
var UiLang = class {
|
|
20714
|
+
current;
|
|
20715
|
+
constructor(initial) {
|
|
20716
|
+
this.current = initial;
|
|
20717
|
+
}
|
|
20718
|
+
/** @returns true when the value changed (worth persisting). */
|
|
20719
|
+
set(tag) {
|
|
20720
|
+
const next = fromTag(tag) ?? "en";
|
|
20721
|
+
if (next === this.current) return false;
|
|
20722
|
+
this.current = next;
|
|
20723
|
+
return true;
|
|
20724
|
+
}
|
|
20725
|
+
get() {
|
|
20726
|
+
return this.current ?? osLang();
|
|
20727
|
+
}
|
|
20728
|
+
/** Explicitly reported (not the OS fallback). */
|
|
20729
|
+
get reported() {
|
|
20730
|
+
return this.current;
|
|
20731
|
+
}
|
|
20732
|
+
};
|
|
20699
20733
|
var protocolNames = (ids, sep) => ids.map((id) => PROTOCOLS.find((p) => p.id === id)?.name ?? id).join(sep);
|
|
20700
20734
|
var TLS_PROTOCOLS = ["hy2", "tuic", "anytls"];
|
|
20701
20735
|
function installPrompt(lang, p) {
|
|
@@ -20738,8 +20772,8 @@ var OUTPUT = {
|
|
|
20738
20772
|
].join("\n")
|
|
20739
20773
|
}]
|
|
20740
20774
|
};
|
|
20741
|
-
function toToolValue(r) {
|
|
20742
|
-
const message = r.error === "not_configured" ? notConfiguredMessage(
|
|
20775
|
+
function toToolValue(r, uiLang) {
|
|
20776
|
+
const message = r.error === "not_configured" ? notConfiguredMessage(uiLang.get()) : r.message;
|
|
20743
20777
|
return {
|
|
20744
20778
|
ok: r.ok,
|
|
20745
20779
|
...r.error ? { error: r.error } : {},
|
|
@@ -20749,6 +20783,16 @@ function toToolValue(r) {
|
|
|
20749
20783
|
}
|
|
20750
20784
|
function apply(ctx, config) {
|
|
20751
20785
|
const service = new VpnService(new CredentialSettingsStore(ctx), { timeoutMs: config.timeoutMs, acmeProvider: config.acmeProvider });
|
|
20786
|
+
const uiLang = new UiLang();
|
|
20787
|
+
readUiLang(ctx).then((lang) => {
|
|
20788
|
+
if (lang && !uiLang.reported) uiLang.set(lang);
|
|
20789
|
+
}, () => {
|
|
20790
|
+
});
|
|
20791
|
+
const persistUiLang = () => {
|
|
20792
|
+
const lang = uiLang.reported;
|
|
20793
|
+
if (lang) writeUiLang(ctx, lang).catch(() => {
|
|
20794
|
+
});
|
|
20795
|
+
};
|
|
20752
20796
|
ctx.tools.register(defineTool({
|
|
20753
20797
|
name: "vpn_install",
|
|
20754
20798
|
description: `Install or reconfigure sing-box on the VPS configured in dsh Settings \u2192 VPN, then return share links. Opens the ports in the VPS firewall automatically (result.firewall). Idempotent: rerunning keeps existing keys and ports unless \`regenerate\` is set. Protocols: ${PROTOCOL_IDS.join(", ")}; omit to use the ones selected in settings.`,
|
|
@@ -20762,7 +20806,7 @@ function apply(ctx, config) {
|
|
|
20762
20806
|
},
|
|
20763
20807
|
output: OUTPUT,
|
|
20764
20808
|
async execute(args, exec) {
|
|
20765
|
-
return toToolValue(await service.install({ protocols: args.protocols, regenerate: args.regenerate }, exec.signal));
|
|
20809
|
+
return toToolValue(await service.install({ protocols: args.protocols, regenerate: args.regenerate }, exec.signal), uiLang);
|
|
20766
20810
|
}
|
|
20767
20811
|
}));
|
|
20768
20812
|
ctx.tools.register(defineTool({
|
|
@@ -20770,21 +20814,21 @@ function apply(ctx, config) {
|
|
|
20770
20814
|
description: "Report whether sing-box is installed, running and listening on the configured VPS.",
|
|
20771
20815
|
parameters: {},
|
|
20772
20816
|
output: OUTPUT,
|
|
20773
|
-
execute: async (_args, exec) => toToolValue(await service.status(exec.signal))
|
|
20817
|
+
execute: async (_args, exec) => toToolValue(await service.status(exec.signal), uiLang)
|
|
20774
20818
|
}));
|
|
20775
20819
|
ctx.tools.register(defineTool({
|
|
20776
20820
|
name: "vpn_links",
|
|
20777
20821
|
description: "Return the share links (vless://, hysteria2://, \u2026) of the installed server. Tell the user they can scan QR codes in dsh Settings \u2192 VPN (\u5237\u65B0\u4E8C\u7EF4\u7801).",
|
|
20778
20822
|
parameters: {},
|
|
20779
20823
|
output: OUTPUT,
|
|
20780
|
-
execute: async (_args, exec) => toToolValue(await service.links(exec.signal))
|
|
20824
|
+
execute: async (_args, exec) => toToolValue(await service.links(exec.signal), uiLang)
|
|
20781
20825
|
}));
|
|
20782
20826
|
ctx.tools.register(defineTool({
|
|
20783
20827
|
name: "vpn_uninstall",
|
|
20784
20828
|
description: "Stop and remove sing-box, its config and credentials from the configured VPS.",
|
|
20785
20829
|
parameters: {},
|
|
20786
20830
|
output: OUTPUT,
|
|
20787
|
-
execute: async (_args, exec) => toToolValue(await service.uninstall(exec.signal))
|
|
20831
|
+
execute: async (_args, exec) => toToolValue(await service.uninstall(exec.signal), uiLang)
|
|
20788
20832
|
}));
|
|
20789
20833
|
if (config.confirm) {
|
|
20790
20834
|
ctx.on("tools/pre-execute", async (exec, next) => {
|
|
@@ -20792,7 +20836,7 @@ function apply(ctx, config) {
|
|
|
20792
20836
|
if (decision.kind !== "allow" || exec.name !== "vpn_install" && exec.name !== "vpn_uninstall") return decision;
|
|
20793
20837
|
const s = await service.store.get();
|
|
20794
20838
|
if (!s) return decision;
|
|
20795
|
-
const lang =
|
|
20839
|
+
const lang = uiLang.get();
|
|
20796
20840
|
const target = `${s.host}:${s.port}`;
|
|
20797
20841
|
if (exec.name === "vpn_uninstall") return { kind: "ask", reason: uninstallPrompt(lang, target) };
|
|
20798
20842
|
const args = exec.arguments;
|
|
@@ -20809,7 +20853,7 @@ function apply(ctx, config) {
|
|
|
20809
20853
|
});
|
|
20810
20854
|
}
|
|
20811
20855
|
ctx.inject(["connection"], (webCtx) => {
|
|
20812
|
-
registerApi(webCtx, service);
|
|
20856
|
+
registerApi(webCtx, service, uiLang, persistUiLang);
|
|
20813
20857
|
});
|
|
20814
20858
|
}
|
|
20815
20859
|
export {
|
package/lib/skill.js
CHANGED
|
@@ -19,7 +19,7 @@ var RESOURCE_BASE = {
|
|
|
19
19
|
};
|
|
20
20
|
var CANDIDATE = {
|
|
21
21
|
name: "vpn-ops",
|
|
22
|
-
description: "Deploy, inspect and troubleshoot a personal
|
|
22
|
+
description: "Deploy, inspect and troubleshoot a personal encrypted VPN (sing-box) on the user's own VPS over ssh, to protect their connections on untrusted networks. Use when the user wants to set up a VPN/proxy node on a server, get node links, or fix a node that stopped working.",
|
|
23
23
|
invocation: { modelInvocable: true, userInvocable: true },
|
|
24
24
|
provider: PROVIDER_NAME,
|
|
25
25
|
source: "bundled",
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vpn",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "DeepSeek Harness plugin:
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "DeepSeek Harness plugin: deploy an encrypted VPN (sing-box) to your own VPS in one click, to protect your connections and privacy on untrusted networks",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"anytls",
|
|
6
7
|
"dsh-plugin",
|
|
7
|
-
"
|
|
8
|
-
"reality",
|
|
8
|
+
"encryption",
|
|
9
9
|
"hysteria2",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"privacy",
|
|
11
|
+
"reality",
|
|
12
|
+
"security",
|
|
12
13
|
"shadowsocks",
|
|
14
|
+
"sing-box",
|
|
15
|
+
"tuic",
|
|
13
16
|
"vpn"
|
|
14
17
|
],
|
|
15
18
|
"license": "MIT",
|