dsh-vpn 0.1.1 → 0.1.2
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 +5 -8
- package/assets/vpn-ops.md +3 -3
- package/lib/client.js +72 -53
- package/lib/index.js +3 -10
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -18,16 +18,13 @@ npx @deepseek-ai/dsh plugin --profile web add dsh-vpn
|
|
|
18
18
|
dsh plugin --profile web add dsh-vpn
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
装好后重启 dsh
|
|
21
|
+
装好后重启 dsh,点左下角「设置」,左侧菜单里的「**VPN 设置**」就是本插件的设置页(dsh 0.1.5 和 0.1.7 相同)。
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
- dsh 0.1.7 及以后:侧边栏「插件」→ dsh-vpn
|
|
25
|
-
|
|
26
|
-
在设置页里:
|
|
23
|
+
在 VPN 设置里:
|
|
27
24
|
|
|
28
25
|
1. 填写服务器 IP、SSH 端口、用户名、密码,点「保存」和「测试连接」。
|
|
29
26
|
2. 勾选要安装的协议,选择证书类型(推荐「IP 证书」),保存后点「安装 / 更新」。
|
|
30
|
-
3.
|
|
27
|
+
3. 在「节点二维码」里点「刷新二维码」,用手机或电脑上的客户端扫码导入。
|
|
31
28
|
4. 在云服务商控制台的安全组里放行页面提示的端口。用 IP 证书时,TCP 80 端口要一直开放,用于续期。
|
|
32
29
|
|
|
33
30
|
## 证书
|
|
@@ -64,7 +61,7 @@ IP 证书由 sing-box 1.14 内置的 ACME 向 Let's Encrypt 申请([2026 年
|
|
|
64
61
|
| `src/settings.ts` / `src/credential-store.ts` | 服务器设置的校验和存储 |
|
|
65
62
|
| `src/api.ts` | 设置页调用的 `/api/dsh-vpn/*` 接口 |
|
|
66
63
|
| `src/index.ts` | agent 工具 `vpn_install` / `vpn_status` / `vpn_links` / `vpn_uninstall`,以及执行前的审批 |
|
|
67
|
-
| `src/client/` |
|
|
64
|
+
| `src/client/` | 浏览器端:设置弹窗里的「VPN 设置」一栏、二维码 |
|
|
68
65
|
| `src/skill.ts` + `assets/vpn-ops.md` | `vpn-ops` 技能:操作流程和排障 |
|
|
69
66
|
|
|
70
67
|
## 远端落盘位置
|
|
@@ -89,7 +86,7 @@ npm 上的 `@deepseek-ai/*` 包版本过旧,所以 `build` 不做类型检查
|
|
|
89
86
|
|
|
90
87
|
## 发布
|
|
91
88
|
|
|
92
|
-
推送与 `package.json` 版本号一致的标签,GitHub Actions 会先跑测试,再发布到 npm
|
|
89
|
+
推送与 `package.json` 版本号一致的标签,GitHub Actions 会先跑测试,再发布到 npm。需要在仓库 Secrets 里配置 `NPM_TOKEN`。仓库是私有的,npm 不支持为私有仓库生成来源证明(provenance),所以发布时不带来源证明:
|
|
93
90
|
|
|
94
91
|
```bash
|
|
95
92
|
git tag v0.1.0 && git push origin v0.1.0
|
package/assets/vpn-ops.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# vpn-ops
|
|
2
2
|
|
|
3
|
-
Deploy and operate a personal sing-box server on the user's VPS. The plugin logs in over SSH with the password saved
|
|
3
|
+
Deploy and operate a personal sing-box server on the user's 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
4
|
|
|
5
5
|
## Workflow
|
|
6
6
|
|
|
7
7
|
1. **Check first.** Call `vpn_status`.
|
|
8
|
-
- `error: not_configured` → tell the user to open dsh
|
|
8
|
+
- `error: not_configured` → tell the user to open dsh 设置 (Settings, bottom left) → VPN 设置, fill in server IP, SSH port, username and password, then click 保存 and 测试连接. Never ask for the password in chat.
|
|
9
9
|
- Installed and active → offer `vpn_links` instead of reinstalling.
|
|
10
10
|
2. **Install.** Call `vpn_install`. Omit `protocols` to use the selection saved in settings. Only pass `regenerate: true` when the user explicitly wants new keys, because every old link stops working.
|
|
11
|
-
3. **Deliver.** Say the links are ready and that
|
|
11
|
+
3. **Deliver.** Say the links are ready and that 设置 → VPN 设置 shows scannable QR codes (刷新二维码 button). Client apps: v2rayN, NekoBox, Shadowrocket, sing-box, Clash Meta / mihomo.
|
|
12
12
|
4. **Remind** the user to open the reported ports in the cloud provider's security group, using the right transport for each protocol.
|
|
13
13
|
|
|
14
14
|
## Certificates
|
package/lib/client.js
CHANGED
|
@@ -62,6 +62,7 @@ var vpnApi = {
|
|
|
62
62
|
var NS = "dsh-vpn";
|
|
63
63
|
var zh = {
|
|
64
64
|
title: "VPN \u8282\u70B9",
|
|
65
|
+
nav: "VPN \u8BBE\u7F6E",
|
|
65
66
|
description: "\u5728\u4F60\u7684 VPS \u4E0A\u4E00\u952E\u90E8\u7F72 sing-box\uFF0C\u5E76\u7528\u4E8C\u7EF4\u7801\u5BFC\u5165\u624B\u673A\u6216\u7535\u8111\u4E0A\u7684\u4EE3\u7406\u5BA2\u6237\u7AEF\u3002",
|
|
66
67
|
serverSection: "\u670D\u52A1\u5668",
|
|
67
68
|
host: "\u670D\u52A1\u5668 IP \u6216\u57DF\u540D",
|
|
@@ -114,8 +115,12 @@ var zh = {
|
|
|
114
115
|
working: "\u6267\u884C\u4E2D\u2026",
|
|
115
116
|
qrSection: "\u8282\u70B9\u4E8C\u7EF4\u7801",
|
|
116
117
|
qrHint: "\u7528 v2rayN\u3001NekoBox\u3001Shadowrocket\u3001sing-box\u3001Clash Meta \u7B49\u5BA2\u6237\u7AEF\u626B\u63CF\u5BFC\u5165\u3002\u4E8C\u7EF4\u7801\u7B49\u540C\u4E8E\u5BC6\u7801\uFF0C\u8BF7\u52FF\u5916\u4F20\u3002",
|
|
117
|
-
refresh: "\u5237\u65B0",
|
|
118
|
-
|
|
118
|
+
refresh: "\u5237\u65B0\u4E8C\u7EF4\u7801",
|
|
119
|
+
refreshing: "\u6B63\u5728\u5237\u65B0\u2026",
|
|
120
|
+
refreshedAt: "\u4E0A\u6B21\u5237\u65B0 {time}",
|
|
121
|
+
generate: "\u751F\u6210\u8282\u70B9\u4E8C\u7EF4\u7801",
|
|
122
|
+
qrEmpty: "\u8FD8\u6CA1\u6709\u4E8C\u7EF4\u7801\u3002\u5B89\u88C5\u5B8C\u6210\u540E\uFF0C\u70B9\u4E0B\u9762\u7684\u6309\u94AE\u4ECE\u670D\u52A1\u5668\u8BFB\u53D6\u8282\u70B9\u5E76\u751F\u6210\u4E8C\u7EF4\u7801\u3002",
|
|
123
|
+
qrNeedsSave: "\u8BF7\u5148\u586B\u5199\u5E76\u4FDD\u5B58\u670D\u52A1\u5668\u4FE1\u606F\u3002",
|
|
119
124
|
copy: "\u590D\u5236\u94FE\u63A5",
|
|
120
125
|
copied: "\u5DF2\u590D\u5236",
|
|
121
126
|
portLabel: "\u7AEF\u53E3 {port}",
|
|
@@ -145,6 +150,7 @@ var zh = {
|
|
|
145
150
|
};
|
|
146
151
|
var en = {
|
|
147
152
|
title: "VPN node",
|
|
153
|
+
nav: "VPN",
|
|
148
154
|
description: "Deploy sing-box to your VPS in one click and import it into proxy apps by scanning QR codes.",
|
|
149
155
|
serverSection: "Server",
|
|
150
156
|
host: "Server IP or domain",
|
|
@@ -197,8 +203,12 @@ var en = {
|
|
|
197
203
|
working: "Working\u2026",
|
|
198
204
|
qrSection: "Node QR codes",
|
|
199
205
|
qrHint: "Scan with v2rayN, NekoBox, Shadowrocket, sing-box, Clash Meta and similar apps. A QR code is as secret as a password.",
|
|
200
|
-
refresh: "Refresh",
|
|
201
|
-
|
|
206
|
+
refresh: "Refresh QR codes",
|
|
207
|
+
refreshing: "Refreshing\u2026",
|
|
208
|
+
refreshedAt: "Last refreshed {time}",
|
|
209
|
+
generate: "Generate node QR codes",
|
|
210
|
+
qrEmpty: "No QR codes yet. After installing, click below to read the nodes from the server and generate QR codes.",
|
|
211
|
+
qrNeedsSave: "Fill in and save the server details first.",
|
|
202
212
|
copy: "Copy link",
|
|
203
213
|
copied: "Copied",
|
|
204
214
|
portLabel: "Port {port}",
|
|
@@ -263,16 +273,21 @@ var STYLES = `
|
|
|
263
273
|
.dshvpn-qr { background: #fff; padding: 6px; border-radius: 6px; line-height: 0; align-self: center; }
|
|
264
274
|
.dshvpn-qr svg { width: 180px; height: 180px; }
|
|
265
275
|
.dshvpn-link { display: block; max-width: 100%; max-height: 3.2em; overflow: hidden; color: var(--dsw-alias-label-secondary, #666); }
|
|
266
|
-
.dshvpn-
|
|
267
|
-
.dshvpn-
|
|
268
|
-
.dshvpn-
|
|
269
|
-
.dshvpn-
|
|
270
|
-
.dshvpn-
|
|
276
|
+
.dshvpn-section { display: flex; flex-direction: column; gap: 16px; }
|
|
277
|
+
.dshvpn-section-head h2 { margin: 0 0 4px; font-size: 18px; font-weight: 600; }
|
|
278
|
+
.dshvpn-section-head p { margin: 0; }
|
|
279
|
+
.dshvpn-qr-section { padding: 14px; border-radius: 12px; border: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3)); }
|
|
280
|
+
.dshvpn-qr-section h3 { font-size: 15px; }
|
|
281
|
+
.dshvpn-refresh { gap: 6px; font-weight: 600; }
|
|
282
|
+
.dshvpn-refresh-lg { padding: 10px 22px !important; font-size: 14px !important; }
|
|
283
|
+
.dshvpn-spin { display: inline-block; animation: dshvpn-spin 1s linear infinite; }
|
|
284
|
+
@keyframes dshvpn-spin { to { transform: rotate(360deg); } }
|
|
285
|
+
.dshvpn-qr-empty { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 28px 16px; text-align: center;
|
|
286
|
+
border: 2px dashed var(--dsw-alias-border-l3, rgba(127,127,127,.35)); border-radius: 12px; }
|
|
287
|
+
.dshvpn-qr-empty p { margin: 0; font-size: 13px; color: var(--dsw-alias-label-secondary, #666); max-width: 420px; }
|
|
288
|
+
.dshvpn-qr-empty-icon { font-size: 36px; line-height: 1; color: var(--dsw-alias-label-tertiary, #888); }
|
|
271
289
|
`;
|
|
272
290
|
|
|
273
|
-
// src/client/VpnSettingsCard.tsx
|
|
274
|
-
var import_react2 = require("react");
|
|
275
|
-
|
|
276
291
|
// src/client/VpnSettingsPage.tsx
|
|
277
292
|
var import_react = require("react");
|
|
278
293
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
@@ -1044,6 +1059,7 @@ function VpnSettingsPage({ view, t, api }) {
|
|
|
1044
1059
|
const [busy, setBusy] = (0, import_react.useState)(null);
|
|
1045
1060
|
const [notice, setNotice] = (0, import_react.useState)(null);
|
|
1046
1061
|
const [links, setLinks] = (0, import_react.useState)(null);
|
|
1062
|
+
const [refreshedAt, setRefreshedAt] = (0, import_react.useState)(null);
|
|
1047
1063
|
const [regenerate, setRegenerate] = (0, import_react.useState)(false);
|
|
1048
1064
|
const [confirming, setConfirming] = (0, import_react.useState)(null);
|
|
1049
1065
|
(0, import_react.useEffect)(() => {
|
|
@@ -1099,13 +1115,21 @@ function VpnSettingsPage({ view, t, api }) {
|
|
|
1099
1115
|
});
|
|
1100
1116
|
const install = async () => {
|
|
1101
1117
|
const r = await op("install", () => api.install(regenerate), (v) => {
|
|
1102
|
-
if (v.links)
|
|
1118
|
+
if (v.links) {
|
|
1119
|
+
setLinks(v.links);
|
|
1120
|
+
setRefreshedAt(/* @__PURE__ */ new Date());
|
|
1121
|
+
}
|
|
1103
1122
|
return { tone: "success", text: `${t("installed")} ${v.links ? portsReminder(v.links) : ""}` };
|
|
1104
1123
|
});
|
|
1105
1124
|
if (!r.ok && r.links) setLinks(r.links);
|
|
1106
1125
|
setRegenerate(false);
|
|
1107
1126
|
void refreshSettings();
|
|
1108
1127
|
};
|
|
1128
|
+
const refreshLinks = () => op("links", api.links, (r) => {
|
|
1129
|
+
if (r.links) setLinks(r.links);
|
|
1130
|
+
setRefreshedAt(/* @__PURE__ */ new Date());
|
|
1131
|
+
return null;
|
|
1132
|
+
});
|
|
1109
1133
|
const disabled = busy !== null;
|
|
1110
1134
|
const needsSave = dirty || !saved.configured;
|
|
1111
1135
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshvpn", children: [
|
|
@@ -1312,53 +1336,51 @@ ${t("certExpires", { date: new Date(cert.expires ?? "").toLocaleString(), hours
|
|
|
1312
1336
|
] })
|
|
1313
1337
|
] }),
|
|
1314
1338
|
(busy !== null || notice) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `dshvpn-notice dshvpn-${busy !== null ? "info" : notice?.tone}`, role: "status", children: busy !== null ? t("working") : notice?.text }),
|
|
1315
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dshvpn-qr-section", children: [
|
|
1316
1340
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshvpn-row", children: [
|
|
1317
1341
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: t("qrSection") }),
|
|
1318
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1319
|
-
|
|
1320
|
-
{
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
return null;
|
|
1328
|
-
});
|
|
1329
|
-
},
|
|
1330
|
-
children: t("refresh")
|
|
1331
|
-
}
|
|
1332
|
-
)
|
|
1342
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dshvpn-actions", children: [
|
|
1343
|
+
refreshedAt && busy !== "links" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "dshvpn-muted", children: t("refreshedAt", { time: refreshedAt.toLocaleTimeString() }) }),
|
|
1344
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dsh_client_ui_primitives.Button, { variant: "primary", className: "dshvpn-refresh", disabled: disabled || needsSave, onClick: () => {
|
|
1345
|
+
void refreshLinks();
|
|
1346
|
+
}, children: [
|
|
1347
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: busy === "links" ? "dshvpn-spin" : void 0, "aria-hidden": "true", children: "\u21BB" }),
|
|
1348
|
+
busy === "links" ? t("refreshing") : t("refresh")
|
|
1349
|
+
] })
|
|
1350
|
+
] })
|
|
1333
1351
|
] }),
|
|
1334
1352
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "dshvpn-muted", children: t("qrHint") }),
|
|
1335
|
-
links ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dshvpn-qr-grid", children: links.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QrCard, { item, t }, item.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime.
|
|
1353
|
+
links ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dshvpn-qr-grid", children: links.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QrCard, { item, t }, item.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshvpn-qr-empty", children: [
|
|
1354
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshvpn-qr-empty-icon", "aria-hidden": "true", children: "\u25A6" }),
|
|
1355
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: needsSave ? t("qrNeedsSave") : t("qrEmpty") }),
|
|
1356
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dsh_client_ui_primitives.Button, { variant: "primary", className: "dshvpn-refresh dshvpn-refresh-lg", disabled: disabled || needsSave, onClick: () => {
|
|
1357
|
+
void refreshLinks();
|
|
1358
|
+
}, children: [
|
|
1359
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: busy === "links" ? "dshvpn-spin" : void 0, "aria-hidden": "true", children: "\u21BB" }),
|
|
1360
|
+
busy === "links" ? t("refreshing") : t("generate")
|
|
1361
|
+
] })
|
|
1362
|
+
] })
|
|
1336
1363
|
] })
|
|
1337
1364
|
] });
|
|
1338
1365
|
}
|
|
1339
1366
|
|
|
1340
|
-
// src/client/
|
|
1367
|
+
// src/client/VpnSettingsSection.tsx
|
|
1341
1368
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1342
|
-
function
|
|
1369
|
+
function VpnSettingsSection(props) {
|
|
1343
1370
|
const { t } = props;
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
}, children: [
|
|
1349
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dshvpn-card-title", children: [
|
|
1350
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: t("title") }),
|
|
1351
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "dshvpn-muted", children: t("description") })
|
|
1352
|
-
] }),
|
|
1353
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", className: "dshvpn-card-chevron", children: open ? "\u25BE" : "\u25B8" })
|
|
1371
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dshvpn-section", children: [
|
|
1372
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "dshvpn-section-head", children: [
|
|
1373
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { children: t("nav") }),
|
|
1374
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dshvpn-muted", children: t("description") })
|
|
1354
1375
|
] }),
|
|
1355
|
-
|
|
1376
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VpnSettingsPage, { t, api: props.api, view: "page" })
|
|
1356
1377
|
] });
|
|
1357
1378
|
}
|
|
1358
1379
|
|
|
1359
1380
|
// src/client/index.ts
|
|
1360
1381
|
var PACKAGE_NAME = "dsh-vpn";
|
|
1361
1382
|
var inject = ["slots", "locale"];
|
|
1383
|
+
var SECTION_ORDER = 25;
|
|
1362
1384
|
function apply(ctx) {
|
|
1363
1385
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-vpn: dictionaries");
|
|
1364
1386
|
ctx.effect(() => {
|
|
@@ -1370,18 +1392,15 @@ function apply(ctx) {
|
|
|
1370
1392
|
style.remove();
|
|
1371
1393
|
};
|
|
1372
1394
|
}, "dsh-vpn: styles");
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
1380
|
-
name: "settings.plugin.item",
|
|
1381
|
-
key: PACKAGE_NAME,
|
|
1395
|
+
const t = ctx.locale.bind(NS);
|
|
1396
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1397
|
+
name: "settings.section",
|
|
1398
|
+
id: PACKAGE_NAME,
|
|
1399
|
+
order: SECTION_ORDER,
|
|
1400
|
+
label: () => t("nav"),
|
|
1382
1401
|
locale: NS,
|
|
1383
1402
|
inject: () => ({ api: vpnApi })
|
|
1384
|
-
},
|
|
1403
|
+
}, VpnSettingsSection));
|
|
1385
1404
|
}
|
|
1386
1405
|
|
|
1387
1406
|
return module.exports;
|
package/lib/index.js
CHANGED
|
@@ -20706,7 +20706,7 @@ function uninstallPrompt(lang, target) {
|
|
|
20706
20706
|
return lang === "zh" ? `\u5C06\u505C\u6B62\u5E76\u5220\u9664 ${target} \u4E0A\u7684 sing-box \u670D\u52A1\u3001\u914D\u7F6E\u548C\u5BC6\u94A5\u3002\u73B0\u6709\u8282\u70B9\u94FE\u63A5\u5C06\u5168\u90E8\u5931\u6548\u3002` : `Stop and remove the sing-box service, its config and keys from ${target}. All existing node links stop working.`;
|
|
20707
20707
|
}
|
|
20708
20708
|
function notConfiguredMessage(lang) {
|
|
20709
|
-
return lang === "zh" ? "\u5C1A\u672A\u914D\u7F6E\u670D\u52A1\u5668\u3002\u8BF7\
|
|
20709
|
+
return lang === "zh" ? "\u5C1A\u672A\u914D\u7F6E\u670D\u52A1\u5668\u3002\u8BF7\u70B9 dsh \u5DE6\u4E0B\u89D2\u300C\u8BBE\u7F6E\u300D\u2192\u300CVPN \u8BBE\u7F6E\u300D\uFF0C\u586B\u5199\u670D\u52A1\u5668 IP\u3001\u7AEF\u53E3\u3001\u7528\u6237\u540D\u548C\u5BC6\u7801\u3002" : "No server configured. Open dsh Settings (bottom left) \u2192 VPN and fill in the server IP, port, username and password first.";
|
|
20710
20710
|
}
|
|
20711
20711
|
|
|
20712
20712
|
// src/index.ts
|
|
@@ -20751,7 +20751,7 @@ function apply(ctx, config) {
|
|
|
20751
20751
|
const service = new VpnService(new CredentialSettingsStore(ctx), { timeoutMs: config.timeoutMs, acmeProvider: config.acmeProvider });
|
|
20752
20752
|
ctx.tools.register(defineTool({
|
|
20753
20753
|
name: "vpn_install",
|
|
20754
|
-
description: `Install or reconfigure sing-box on the VPS configured in
|
|
20754
|
+
description: `Install or reconfigure sing-box on the VPS configured in dsh Settings \u2192 VPN, then return share links. Idempotent: rerunning keeps existing keys and ports unless \`regenerate\` is set. Protocols: ${PROTOCOL_IDS.join(", ")}; omit to use the ones selected in settings.`,
|
|
20755
20755
|
parameters: {
|
|
20756
20756
|
protocols: {
|
|
20757
20757
|
type: "array",
|
|
@@ -20774,7 +20774,7 @@ function apply(ctx, config) {
|
|
|
20774
20774
|
}));
|
|
20775
20775
|
ctx.tools.register(defineTool({
|
|
20776
20776
|
name: "vpn_links",
|
|
20777
|
-
description: "Return the share links (vless://, hysteria2://, \u2026) of the installed server. Tell the user they can scan QR codes
|
|
20777
|
+
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
20778
|
parameters: {},
|
|
20779
20779
|
output: OUTPUT,
|
|
20780
20780
|
execute: async (_args, exec) => toToolValue(await service.links(exec.signal))
|
|
@@ -20811,13 +20811,6 @@ function apply(ctx, config) {
|
|
|
20811
20811
|
ctx.inject(["connection"], (webCtx) => {
|
|
20812
20812
|
registerApi(webCtx, service);
|
|
20813
20813
|
});
|
|
20814
|
-
ctx.inject(["settings"], (settingsCtx) => {
|
|
20815
|
-
const settings = settingsCtx.settings;
|
|
20816
|
-
try {
|
|
20817
|
-
settings?.register?.("dsh-vpn", Schema.object({}), {});
|
|
20818
|
-
} catch {
|
|
20819
|
-
}
|
|
20820
|
-
});
|
|
20821
20814
|
}
|
|
20822
20815
|
export {
|
|
20823
20816
|
Config,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vpn",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "DeepSeek Harness plugin: one-click sing-box deployment to a VPS over SSH, with a settings page and QR codes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh-plugin",
|
|
@@ -53,8 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"packageManager": "pnpm@11.9.0",
|
|
55
55
|
"publishConfig": {
|
|
56
|
-
"access": "public"
|
|
57
|
-
"provenance": true
|
|
56
|
+
"access": "public"
|
|
58
57
|
},
|
|
59
58
|
"scripts": {
|
|
60
59
|
"build": "node scripts/build.mjs",
|