@mrrisega/dsh-remote 0.3.8 → 0.4.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/dsh-setup.mjs +71 -46
- package/package.json +1 -1
- package/packages/dsh-remote-ui/lib/index.js +15 -8
package/dsh-setup.mjs
CHANGED
|
@@ -99,6 +99,19 @@ function deriveTunnelUrl(apiUrl) {
|
|
|
99
99
|
return apiUrl.replace(/\/relay-api\/?$/, "").replace(/^https/, "wss");
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* 模式归一化(SaaS 权威):清掉自建残留(local_key/假 tunnel_url),api_url 与
|
|
104
|
+
* tunnel_url 一律按云端权威地址重算。防止"切过自建后又登云端"时残留配置把
|
|
105
|
+
* bridge 带到错误服务器(此前手机永远看不到设备的根因之一)。
|
|
106
|
+
*/
|
|
107
|
+
function applySaaSMode(cfg) {
|
|
108
|
+
delete cfg.local_key;
|
|
109
|
+
delete cfg.server; // 旧字段兜底
|
|
110
|
+
cfg.api_url = String(cfg.api_url || DEFAULT_API).replace(/\/+$/, "");
|
|
111
|
+
cfg.tunnel_url = deriveTunnelUrl(cfg.api_url);
|
|
112
|
+
return cfg;
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
/** 归一化自建服务器地址:缺省补 wss://,去掉末尾 / */
|
|
103
116
|
function normalizeTunnelUrl(raw) {
|
|
104
117
|
let u = String(raw || "").trim().replace(/\/+$/, "");
|
|
@@ -239,9 +252,7 @@ function serveSettings() {
|
|
|
239
252
|
return send(400, JSON.stringify({ ok: false, message: "SaaS 模式需要手机号与密码" }), "application/json");
|
|
240
253
|
}
|
|
241
254
|
cfg.phone = body.phone; cfg.password = body.password;
|
|
242
|
-
|
|
243
|
-
cfg.api_url = body.api_url || cfg.api_url || DEFAULT_API;
|
|
244
|
-
if (!cfg.tunnel_url) cfg.tunnel_url = deriveTunnelUrl(cfg.api_url);
|
|
255
|
+
applySaaSMode(cfg); // 清自建残留,api/tunnel 权威重算
|
|
245
256
|
}
|
|
246
257
|
// 自动获取服务端下发的 bridge_secret(device-login 共享密钥,一键安装开箱即用)
|
|
247
258
|
if (!cfg.bridge_secret && !cfg.local_key) {
|
|
@@ -328,7 +339,7 @@ async function runBridge() {
|
|
|
328
339
|
// 每次循环重读配置:设置页登录/切换模式后无需重启守护即可生效
|
|
329
340
|
cfg = loadConfig();
|
|
330
341
|
const saas = Boolean((cfg.phone || cfg.email) && cfg.password);
|
|
331
|
-
|
|
342
|
+
let local = Boolean(cfg.local_key);
|
|
332
343
|
if (!saas && !local) {
|
|
333
344
|
if (!warnedNoLogin) {
|
|
334
345
|
warnedNoLogin = true;
|
|
@@ -341,9 +352,13 @@ async function runBridge() {
|
|
|
341
352
|
console.log("⚠️ 自建模式缺少服务器地址:请用 `dsh-remote setup --server wss://host:port --key <密钥>` 重新配置。");
|
|
342
353
|
return;
|
|
343
354
|
}
|
|
344
|
-
if (saas
|
|
345
|
-
|
|
355
|
+
if (saas) {
|
|
356
|
+
// SaaS 权威归一化:清掉任何自建残留,tunnel/api 始终指向云端地址
|
|
357
|
+
applySaaSMode(cfg);
|
|
358
|
+
local = false;
|
|
346
359
|
saveConfig(cfg);
|
|
360
|
+
} else if (local && !cfg.tunnel_url) {
|
|
361
|
+
cfg.tunnel_url = deriveTunnelUrl(cfg.api_url || DEFAULT_API);
|
|
347
362
|
}
|
|
348
363
|
const apiUrl = saas ? (cfg.api_url || DEFAULT_API) : "";
|
|
349
364
|
|
|
@@ -458,7 +473,8 @@ async function setup(argv) {
|
|
|
458
473
|
console.log(` 手机端: 打开 ${cfg.tunnel_url.replace(/^ws/, "https")}/app/ ,用访问密钥登录即可。`);
|
|
459
474
|
} else {
|
|
460
475
|
console.log(` 远程控制地址: ${pub.app_url || DEFAULT_APP_URL}`);
|
|
461
|
-
console.log(`
|
|
476
|
+
console.log(` 下一步(小白/无需任何命令): 打开 dsh web → 设置 → 「远程控制」→ 注册/登录手机号即可。`);
|
|
477
|
+
console.log(` 若你是从 DeepSeek App/插件市场 安装:请【完全退出并重开 App】(或刷新 profile),插件即生效。`);
|
|
462
478
|
console.log(` 登录后 bridge 会自动启动,手机端即可看到本机。`);
|
|
463
479
|
}
|
|
464
480
|
if (svc.path) console.log(` 自启动服务: ${svc.path}`);
|
|
@@ -469,22 +485,23 @@ async function setup(argv) {
|
|
|
469
485
|
// ---------- plugin:安装/卸载 dsh web 远程控制插件 ----------
|
|
470
486
|
const PLUGIN_MARKER_START = "# >>> dsh-remote-ui (managed by dsh-remote plugin; do not edit)";
|
|
471
487
|
const PLUGIN_MARKER_END = "# <<< dsh-remote-ui";
|
|
472
|
-
/** 插件在 profile 内的固定本地目录(
|
|
488
|
+
/** 插件在 profile 内的固定本地目录(安装时整目录拷贝)。 */
|
|
473
489
|
const PLUGIN_LOCAL_DIR = "dsh-remote-ui-plugin";
|
|
474
|
-
/** 插件 node 半入口(相对 profile 目录;加载器按 '.' 开头在配置文件旁解析)。 */
|
|
475
|
-
const PLUGIN_ENTRY_REL = `./${PLUGIN_LOCAL_DIR}/lib/index.js`;
|
|
476
490
|
|
|
477
491
|
/**
|
|
478
|
-
* include 块。
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
492
|
+
* include 块。name 必须是【裸包名】'dsh-remote-ui':
|
|
493
|
+
* - 加载器据此 import 节点半(dsh-remote-ui 由 node_modules 链接解析);
|
|
494
|
+
* - dsh 客户端模块系统(@deepseek-ai/dsh-client-modules)用 require.resolve(
|
|
495
|
+
* '<name>/package.json') 从 profile 解析该包并读取 package.json 的
|
|
496
|
+
* dsh.client 声明 → 注入浏览器半(设置面板 UI)。相对文件路径入口只会加载
|
|
497
|
+
* 节点半、不会注入 UI —— 0.3.7/0.3.8 曾因此丢设置页。
|
|
498
|
+
* 链接由安装器自建(node_modules/dsh-remote-ui → 拷贝出的目录),不依赖 pnpm/npm。
|
|
482
499
|
*/
|
|
483
500
|
function pluginBlock(relayDir) {
|
|
484
501
|
return `${PLUGIN_MARKER_START}
|
|
485
502
|
- insert:
|
|
486
503
|
- id: dsh-remote-ui
|
|
487
|
-
name: '
|
|
504
|
+
name: 'dsh-remote-ui'
|
|
488
505
|
config:
|
|
489
506
|
relayDir: '${relayDir}'
|
|
490
507
|
${PLUGIN_MARKER_END}`;
|
|
@@ -559,27 +576,29 @@ function copyPluginIntoProfile(profileDir, pluginDir) {
|
|
|
559
576
|
return dest;
|
|
560
577
|
}
|
|
561
578
|
|
|
562
|
-
/**
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
function cleanupLegacyPluginState(profileDir, pkgFile) {
|
|
568
|
-
let changed = false;
|
|
579
|
+
/** 把插件挂到 <profile>/node_modules/dsh-remote-ui(裸包名解析需要),指向拷贝目录;缺失/指错时重建。 */
|
|
580
|
+
function ensurePluginLinked(profileDir, pluginLocalDir) {
|
|
581
|
+
const nmDir = path.join(profileDir, "node_modules");
|
|
582
|
+
const nmPlugin = path.join(nmDir, "dsh-remote-ui");
|
|
583
|
+
fs.mkdirSync(nmDir, { recursive: true });
|
|
569
584
|
try {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + "\n");
|
|
574
|
-
changed = true;
|
|
575
|
-
}
|
|
576
|
-
} catch { /* 无 package.json 或损坏则跳过 */ }
|
|
585
|
+
if (fs.realpathSync(nmPlugin) === pluginLocalDir) return false;
|
|
586
|
+
} catch { /* 缺失/悬空 → 重建 */ }
|
|
587
|
+
try { fs.rmSync(nmPlugin, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
577
588
|
try {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
return
|
|
589
|
+
fs.symlinkSync(pluginLocalDir, nmPlugin, "dir");
|
|
590
|
+
} catch {
|
|
591
|
+
fs.cpSync(pluginLocalDir, nmPlugin, { recursive: true });
|
|
592
|
+
}
|
|
593
|
+
try { return fs.realpathSync(nmPlugin) === pluginLocalDir; } catch { return false; }
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** package.json 写入 file: 依赖(让后续任何 pnpm/npm install 也认可该插件,不会删链接)。 */
|
|
597
|
+
function declarePluginDep(pkgFile) {
|
|
598
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf8"));
|
|
599
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
600
|
+
pkg.dependencies["dsh-remote-ui"] = `file:./${PLUGIN_LOCAL_DIR}`;
|
|
601
|
+
fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + "\n");
|
|
583
602
|
}
|
|
584
603
|
|
|
585
604
|
async function pluginCmd(argv) {
|
|
@@ -614,21 +633,25 @@ async function pluginCmd(argv) {
|
|
|
614
633
|
console.log("ℹ patch 中未发现 dsh-remote-ui 条目。");
|
|
615
634
|
}
|
|
616
635
|
fs.rmSync(path.join(profileDir, PLUGIN_LOCAL_DIR), { recursive: true, force: true });
|
|
617
|
-
|
|
618
|
-
|
|
636
|
+
fs.rmSync(path.join(profileDir, "node_modules", "dsh-remote-ui"), { recursive: true, force: true });
|
|
637
|
+
try {
|
|
638
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf8"));
|
|
639
|
+
if (pkg.dependencies) delete pkg.dependencies["dsh-remote-ui"];
|
|
640
|
+
fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + "\n");
|
|
641
|
+
} catch { /* ignore */ }
|
|
619
642
|
console.log("✅ 卸载完成。重启 dsh web 生效。");
|
|
620
643
|
return;
|
|
621
644
|
}
|
|
622
645
|
|
|
623
|
-
//
|
|
624
|
-
//
|
|
646
|
+
// 安装 = 拷贝完整插件目录 + 写裸名 include + 自建 node_modules 链接 + 声明 file: 依赖。
|
|
647
|
+
// 全程不跑 pnpm/npm;但链接与依赖双保险,任何包管理器之后 install 也不会破坏它。
|
|
625
648
|
const pluginLocalDir = copyPluginIntoProfile(profileDir, pluginDir);
|
|
626
649
|
const entryFile = path.join(pluginLocalDir, "lib", "index.js");
|
|
627
650
|
if (!fs.existsSync(entryFile)) {
|
|
628
|
-
console.error(`❌ 插件入口缺失:${entryFile}
|
|
651
|
+
console.error(`❌ 插件入口缺失:${entryFile}(本包不完整?请用官方源重装:npx --registry=https://registry.npmjs.org @mrrisega/dsh-remote@latest)`);
|
|
629
652
|
process.exit(1);
|
|
630
653
|
}
|
|
631
|
-
console.log(`✅ 插件已拷贝到 ${pluginLocalDir}
|
|
654
|
+
console.log(`✅ 插件已拷贝到 ${pluginLocalDir}`);
|
|
632
655
|
|
|
633
656
|
// 先清掉旧条目(含旧版无标记条目),再写入带标记的新块,保证不重复。
|
|
634
657
|
// 关键:先清除默认的 [] 空文档占位行,否则拼接出的 YAML 非法,dsh web 启动即崩。
|
|
@@ -636,15 +659,17 @@ async function pluginCmd(argv) {
|
|
|
636
659
|
const base = normalizePatchBase(stripped);
|
|
637
660
|
const block = pluginBlock(CONFIG_DIR);
|
|
638
661
|
fs.writeFileSync(patchFile, (base ? base + "\n" : "") + block + "\n");
|
|
639
|
-
console.log(`✅ 已写入 ${patchFile}
|
|
662
|
+
console.log(`✅ 已写入 ${patchFile}(裸名 include:dsh-remote-ui → 节点半 + 浏览器半均生效)`);
|
|
640
663
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
664
|
+
declarePluginDep(pkgFile); // package.json 声明 file: 依赖(后端各类 install 不误删)
|
|
665
|
+
const linked = ensurePluginLinked(profileDir, pluginLocalDir); // 自建 node_modules 链接
|
|
666
|
+
console.log(linked
|
|
667
|
+
? `✅ 已建立 node_modules/dsh-remote-ui → ${pluginLocalDir}(免包管理器即可解析)`
|
|
668
|
+
: "✅ node_modules/dsh-remote-ui 已就绪");
|
|
645
669
|
|
|
646
670
|
console.log(`✅ 插件安装完成。配置目录: ${CONFIG_DIR}`);
|
|
647
|
-
console.log("
|
|
671
|
+
console.log(" 打开 dsh web → 设置 → 「远程控制」,注册/登录手机号即可(无需任何命令)。");
|
|
672
|
+
console.log(" 若从 DeepSeek App/插件市场 安装:请完全退出并重开 App 让插件生效。");
|
|
648
673
|
}
|
|
649
674
|
|
|
650
675
|
// ---------- main ----------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrisega/dsh-remote",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "手机远程控制 DeepSeek Harness · Remote control DeepSeek Harness (dsh web) from any phone browser — 100% 全功能 App 级体验:发消息、看工具执行、审批权限、改设置、管凭据,含特权操作,免内网穿透。一条命令安装 npx @mrrisega/dsh-remote。Mobile remote control for DSH, self-host or SaaS, no server needed on LAN.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -106,6 +106,19 @@ function saveConfig(relayDir, cfg) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* SaaS 模式权威归一化(与 dsh-setup.mjs 同规则):
|
|
111
|
+
* 清除自建残留(local_key/假 tunnel_url),api_url 与 tunnel_url 一律按云端权威地址重算。
|
|
112
|
+
* 解决“切过自建(填了假地址)后,再登/重装云端账号仍连错服务器、手机看不到设备”的残留配置问题。
|
|
113
|
+
*/
|
|
114
|
+
function applySaaSMode(cfg) {
|
|
115
|
+
delete cfg.local_key;
|
|
116
|
+
delete cfg.server;
|
|
117
|
+
cfg.api_url = String(cfg.api_url || DEFAULT_API).replace(/\/+$/, "");
|
|
118
|
+
cfg.tunnel_url = cfg.api_url.replace(/\/relay-api\/?$/, "").replace(/^https/, "wss");
|
|
119
|
+
return cfg;
|
|
120
|
+
}
|
|
121
|
+
|
|
109
122
|
// ---------- bridge 服务状态 / 启停(launchctl,macOS) ----------
|
|
110
123
|
|
|
111
124
|
function launchAgentPath() {
|
|
@@ -524,14 +537,8 @@ function registerRoutes(ctx, relayDir) {
|
|
|
524
537
|
delete cfg.device_private_key;
|
|
525
538
|
delete cfg.device_public_key;
|
|
526
539
|
}
|
|
527
|
-
//
|
|
528
|
-
|
|
529
|
-
const wasLocal = Boolean(cfg.local_key);
|
|
530
|
-
delete cfg.local_key;
|
|
531
|
-
if (wasLocal) {
|
|
532
|
-
if (cfg.tunnel_url) delete cfg.tunnel_url;
|
|
533
|
-
if (cfg.api_url) delete cfg.api_url;
|
|
534
|
-
}
|
|
540
|
+
// SaaS 权威归一化:清除自建残留(local_key/假 tunnel_url),api/tunnel 一律按云端重算
|
|
541
|
+
applySaaSMode(cfg);
|
|
535
542
|
}
|
|
536
543
|
saveConfig(relayDir, cfg);
|
|
537
544
|
const bridgeRestart = startBridge(relayDir);
|