{"_id":"@captain1275/dsh-remote-web-ui","_rev":"9-ba62ca77e4eafef9e65149e04f240a10","name":"@captain1275/dsh-remote-web-ui","dist-tags":{"latest":"0.2.5"},"versions":{"0.1.12":{"name":"@captain1275/dsh-remote-web-ui","version":"0.1.12","license":"Apache-2.0","_id":"@captain1275/dsh-remote-web-ui@0.1.12","maintainers":[{"name":"captain1275","email":"captain1275@163.com"}],"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-connection","@deepseek-ai/dsh-client-ui-settings","@deepseek-ai/dsh-client-ui-sidebar"],"platform":"web"}},"dist":{"shasum":"58edee21a9178bb9e1c22cd71efeee685de1553f","tarball":"https://registry.npmjs.org/@captain1275/dsh-remote-web-ui/-/dsh-remote-web-ui-0.1.12.tgz","fileCount":185,"integrity":"sha512-BFlIrmTsZpZ1uo/RmWOs3bObG3aPnBowFS35YxdxvvSnF2ZiMIvNYvZgmg+hyiltSsDE0BHUdHQIzn1fpkLIrw==","signatures":[{"sig":"MEQCICJSr7Y+ZPG/ZtpvFT/IGdTHYH3tuBIgHWy+JWBzDMTqAiAPcbG+NeTsRFDyOmS6lVCFT3c8ZOgj2a0JpG9Sh9PNJA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2572455},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19.0 || >=24.0.0"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./client":{"types":"./lib/types/client/index.d.ts","default":"./lib/client.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsc -b && tsdown","typecheck":"tsc -b --pretty false"},"_npmUser":{"name":"captain1275","email":"captain1275@163.com"},"deprecated":"removed: mobile remote-control plugin no longer used; LAN access now goes through the desktop GUI directly","repository":{"url":"https://github.com/zhu1090093659/dsh-web-ui.git","type":"git"},"description":"Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cloudflared":"^0.7.3","schemastery":"^3.18.0","qrcode.react":"^4.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.1.1","react":"^18.2.0","tsdown":"^0.22.2","vitest":"^4.1.8","react-dom":"^18.2.0","typescript":"^6.0.3","@types/node":"^22.20.0","@types/react":"~18.3.1","lightningcss":"^1.32.0","@types/react-dom":"~18.3.0","@deepseek-ai/cordis":"^4.0.1","vite-tsconfig-paths":"^6.1.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-host-apiproxy":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-remote-web-ui_0.1.12_1786720714590_0.7351834707733074","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@captain1275/dsh-remote-web-ui","version":"0.2.0","license":"Apache-2.0","_id":"@captain1275/dsh-remote-web-ui@0.2.0","maintainers":[{"name":"captain1275","email":"captain1275@163.com"}],"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-connection","@deepseek-ai/dsh-client-ui-settings","@deepseek-ai/dsh-client-ui-sidebar"],"platform":"web"}},"dist":{"shasum":"698e2fb0135b41cc3a7c6181bdb34321971b2620","tarball":"https://registry.npmjs.org/@captain1275/dsh-remote-web-ui/-/dsh-remote-web-ui-0.2.0.tgz","fileCount":185,"integrity":"sha512-+CYXq27mYjbx6xXSAywb3ELO5TTkFhgnfUIJakVVXMiPvlP77q/ntEhyT9VOOWBQbsH/cjZMmtxBfJWJm/toaw==","signatures":[{"sig":"MEYCIQDqMNefmHKmhf3CMdvlQTJQleo0687Vcmzp3+6T/+z8ewIhALUy1j3JiLZ7/fuiXlx5oBt2tG6co3p5WkSRZeIU6hLR","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2572452},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19.0 || >=24.0.0"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./client":{"types":"./lib/types/client/index.d.ts","default":"./lib/client.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsc -b && tsdown","typecheck":"tsc -b --pretty false"},"_npmUser":{"name":"captain1275","email":"captain1275@163.com"},"repository":{"url":"https://github.com/CAPTAIN1275/dsh-ui-web.git","type":"git"},"description":"Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cloudflared":"^0.7.3","schemastery":"^3.18.0","qrcode.react":"^4.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.1.1","react":"^18.2.0","tsdown":"^0.22.2","vitest":"^4.1.8","react-dom":"^18.2.0","typescript":"^6.0.3","@types/node":"^22.20.0","@types/react":"~18.3.1","lightningcss":"^1.32.0","@types/react-dom":"~18.3.0","@deepseek-ai/cordis":"^4.0.1","vite-tsconfig-paths":"^6.1.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-host-apiproxy":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-remote-web-ui_0.2.0_1786730976763_0.9984244172388568","host":"s3://npm-registry-packages-npm-production"},"deprecated":"removed: mobile remote-control plugin no longer used; LAN access now goes through the desktop GUI directly"},"0.2.1":{"name":"@captain1275/dsh-remote-web-ui","version":"0.2.1","license":"Apache-2.0","_id":"@captain1275/dsh-remote-web-ui@0.2.1","maintainers":[{"name":"captain1275","email":"captain1275@163.com"}],"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-connection","@deepseek-ai/dsh-client-ui-settings","@deepseek-ai/dsh-client-ui-sidebar"],"platform":"web"}},"dist":{"shasum":"3a5af7558cf71fef85248c8dbac7869cdedc1c8c","tarball":"https://registry.npmjs.org/@captain1275/dsh-remote-web-ui/-/dsh-remote-web-ui-0.2.1.tgz","fileCount":189,"integrity":"sha512-64kMNLqww8GVaxRS+Z40uwFxmPEBNy5FTGRibBdeVH//228Xf0/6B3R4i8Po62GXAG7qvXb6jBhU7kELUGd1gw==","signatures":[{"sig":"MEYCIQCguPyAVe9uh12KNMlN45FXl8yK4v7SoZ6s4qGRp3ZmXwIhAMttIDj+ySJQMvHYhnNS+U2X0yg6P55dibtObesoIY2S","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2583379},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19.0 || >=24.0.0"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./client":{"types":"./lib/types/client/index.d.ts","default":"./lib/client.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsc -b && tsdown","typecheck":"tsc -b --pretty false"},"_npmUser":{"name":"captain1275","email":"captain1275@163.com"},"repository":{"url":"https://github.com/CAPTAIN1275/dsh-ui-web.git","type":"git"},"description":"Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cloudflared":"^0.7.3","schemastery":"^3.18.0","qrcode.react":"^4.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.1.1","react":"^18.2.0","tsdown":"^0.22.2","vitest":"^4.1.8","react-dom":"^18.2.0","typescript":"^6.0.3","@types/node":"^22.20.0","@types/react":"~18.3.1","lightningcss":"^1.32.0","@types/react-dom":"~18.3.0","@deepseek-ai/cordis":"^4.0.1","vite-tsconfig-paths":"^6.1.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-host-apiproxy":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-remote-web-ui_0.2.1_1786731999402_0.7099956236399931","host":"s3://npm-registry-packages-npm-production"},"deprecated":"removed: mobile remote-control plugin no longer used; LAN access now goes through the desktop GUI directly"},"0.2.2":{"name":"@captain1275/dsh-remote-web-ui","version":"0.2.2","license":"Apache-2.0","_id":"@captain1275/dsh-remote-web-ui@0.2.2","maintainers":[{"name":"captain1275","email":"captain1275@163.com"}],"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-connection","@deepseek-ai/dsh-client-ui-settings","@deepseek-ai/dsh-client-ui-sidebar"],"platform":"web"}},"dist":{"shasum":"116c4fab814c0f649096dc73068a130b8253930f","tarball":"https://registry.npmjs.org/@captain1275/dsh-remote-web-ui/-/dsh-remote-web-ui-0.2.2.tgz","fileCount":189,"integrity":"sha512-4A/IgcOktWk79O681NqaPrONv5BAWn7HZ2JTX/NR+hae3ydEThgMj01RsQy3GlTxboZK6KOUgW2x4Q+emtGmXA==","signatures":[{"sig":"MEQCIF1mCjC6GVbpAw8SOYnVUJsxwvwfsJweBXzI4AMpIdYhAiBwmiMMzLJiR4aMdceAgwojGnwz9DHQcyLabJoLQYWQsQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2588866},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19.0 || >=24.0.0"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./client":{"types":"./lib/types/client/index.d.ts","default":"./lib/client.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsc -b && tsdown","typecheck":"tsc -b --pretty false"},"_npmUser":{"name":"captain1275","email":"captain1275@163.com"},"repository":{"url":"https://github.com/CAPTAIN1275/dsh-ui-web.git","type":"git"},"description":"Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cloudflared":"^0.7.3","schemastery":"^3.18.0","qrcode.react":"^4.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.1.1","react":"^18.2.0","tsdown":"^0.22.2","vitest":"^4.1.8","react-dom":"^18.2.0","typescript":"^6.0.3","@types/node":"^22.20.0","@types/react":"~18.3.1","lightningcss":"^1.32.0","@types/react-dom":"~18.3.0","@deepseek-ai/cordis":"^4.0.1","vite-tsconfig-paths":"^6.1.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-host-apiproxy":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-remote-web-ui_0.2.2_1786778345520_0.5596681796263288","host":"s3://npm-registry-packages-npm-production"},"deprecated":"removed: mobile remote-control plugin no longer used; LAN access now goes through the desktop GUI directly"},"0.2.3":{"name":"@captain1275/dsh-remote-web-ui","version":"0.2.3","license":"Apache-2.0","_id":"@captain1275/dsh-remote-web-ui@0.2.3","maintainers":[{"name":"captain1275","email":"captain1275@163.com"}],"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-connection","@deepseek-ai/dsh-client-ui-settings","@deepseek-ai/dsh-client-ui-sidebar"],"platform":"web"}},"dist":{"shasum":"3e69cf6c30781b683790b6fa9bee2ee97b1b616e","tarball":"https://registry.npmjs.org/@captain1275/dsh-remote-web-ui/-/dsh-remote-web-ui-0.2.3.tgz","fileCount":189,"integrity":"sha512-nXCSy+IUHD4u8ZxKFH7WrMJeub3WgpqzEVgr9m/EotngBPf1xmFfN0XkGGqk15YN/om9qekoFcFC6bV92LFIew==","signatures":[{"sig":"MEUCIFQhTGes6H7dIn4p7Ey1FZQri2ThON9Q5LX8lkx+lkmvAiEAjwZ/oNXm6NrkTXXXOTVXdikdFVfJfOgCbJY5aS5XPbs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2588866},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19.0 || >=24.0.0"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./client":{"types":"./lib/types/client/index.d.ts","default":"./lib/client.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsc -b && tsdown","typecheck":"tsc -b --pretty false"},"_npmUser":{"name":"captain1275","email":"captain1275@163.com"},"repository":{"url":"https://github.com/CAPTAIN1275/dsh-ui-web.git","type":"git"},"description":"Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cloudflared":"^0.7.3","schemastery":"^3.18.0","qrcode.react":"^4.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.1.1","react":"^18.2.0","tsdown":"^0.22.2","vitest":"^4.1.8","react-dom":"^18.2.0","typescript":"^6.0.3","@types/node":"^22.20.0","@types/react":"~18.3.1","lightningcss":"^1.32.0","@types/react-dom":"~18.3.0","@deepseek-ai/cordis":"^4.0.1","vite-tsconfig-paths":"^6.1.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-host-apiproxy":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-remote-web-ui_0.2.3_1786795105954_0.6466216348204596","host":"s3://npm-registry-packages-npm-production"},"deprecated":"removed: mobile remote-control plugin no longer used; LAN access now goes through the desktop GUI directly"},"0.2.4":{"name":"@captain1275/dsh-remote-web-ui","version":"0.2.4","license":"Apache-2.0","_id":"@captain1275/dsh-remote-web-ui@0.2.4","maintainers":[{"name":"captain1275","email":"captain1275@163.com"}],"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-connection","@deepseek-ai/dsh-client-ui-settings","@deepseek-ai/dsh-client-ui-sidebar"],"platform":"web"}},"dist":{"shasum":"dc1c939a0268e8ec1da52d8e6a04a9167a6a8049","tarball":"https://registry.npmjs.org/@captain1275/dsh-remote-web-ui/-/dsh-remote-web-ui-0.2.4.tgz","fileCount":189,"integrity":"sha512-0tmheWVSLsoUQWUZYfRxV5+Z3vlByCgke7NnABVeSvfQvGxHbGv2ER5JYQkiFpEpu0oJtwzSaeb+VWz7988ZDA==","signatures":[{"sig":"MEUCIQCrRIPxz3Txu9RTfDRj0snszFwbUO/qNYlINhuOzvP4/gIgdKDbxTSyJE5uYu54wXIH8gekNMT9gVZMgNbVV1Zoojw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2588866},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19.0 || >=24.0.0"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./client":{"types":"./lib/types/client/index.d.ts","default":"./lib/client.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsc -b && tsdown","typecheck":"tsc -b --pretty false"},"_npmUser":{"name":"captain1275","email":"captain1275@163.com"},"repository":{"url":"https://github.com/CAPTAIN1275/dsh-ui-web.git","type":"git"},"description":"Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cloudflared":"^0.7.3","schemastery":"^3.18.0","qrcode.react":"^4.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.1.1","react":"^18.2.0","tsdown":"^0.22.2","vitest":"^4.1.8","react-dom":"^18.2.0","typescript":"^6.0.3","@types/node":"^22.20.0","@types/react":"~18.3.1","lightningcss":"^1.32.0","@types/react-dom":"~18.3.0","@deepseek-ai/cordis":"^4.0.1","vite-tsconfig-paths":"^6.1.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-host-apiproxy":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-remote-web-ui_0.2.4_1786824484674_0.5085125613066623","host":"s3://npm-registry-packages-npm-production"},"deprecated":"removed: mobile remote-control plugin no longer used; LAN access now goes through the desktop GUI directly"},"0.2.5":{"name":"@captain1275/dsh-remote-web-ui","version":"0.2.5","license":"Apache-2.0","_id":"@captain1275/dsh-remote-web-ui@0.2.5","maintainers":[{"name":"captain1275","email":"captain1275@163.com"}],"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-connection","@deepseek-ai/dsh-client-ui-settings","@deepseek-ai/dsh-client-ui-sidebar"],"platform":"web"}},"dist":{"shasum":"b4298530b8a1d18e8e6b743ccec7adbfcc04ce2c","tarball":"https://registry.npmjs.org/@captain1275/dsh-remote-web-ui/-/dsh-remote-web-ui-0.2.5.tgz","fileCount":189,"integrity":"sha512-gXX0PqGbgncvSTtQ0xRe1PAg1opCfdiKYvMnCbTMKDQLMe7sGiZ71MubDS8qEcxxexmskaMWGNxvWvelXDGqcg==","signatures":[{"sig":"MEUCIDHMof970on37wfqWMguV+A2ZHZhY2t6AEhPje/N2um1AiEAzeNTl7GjFYlDIrNOU1Eyl37BkxgRdLHxj/f53PAAEuM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2588866},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19.0 || >=24.0.0"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./client":{"types":"./lib/types/client/index.d.ts","default":"./lib/client.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsc -b && tsdown","typecheck":"tsc -b --pretty false"},"_npmUser":{"name":"captain1275","email":"captain1275@163.com"},"repository":{"url":"https://github.com/CAPTAIN1275/dsh-ui-web.git","type":"git"},"description":"Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cloudflared":"^0.7.3","schemastery":"^3.18.0","qrcode.react":"^4.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.1.1","react":"^18.2.0","tsdown":"^0.22.2","vitest":"^4.1.8","react-dom":"^18.2.0","typescript":"^6.0.3","@types/node":"^22.20.0","@types/react":"~18.3.1","lightningcss":"^1.32.0","@types/react-dom":"~18.3.0","@deepseek-ai/cordis":"^4.0.1","vite-tsconfig-paths":"^6.1.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-host-apiproxy":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@deepseek-ai/dsh-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-invariants":"^0.1.0-rc.6","@deepseek-ai/dsh-client-locale":"^0.1.0-rc.6","@deepseek-ai/dsh-client-runtime":"^0.1.0-rc.6","@deepseek-ai/dsh-host-webserver":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-slots":"^0.1.0-rc.6","@deepseek-ai/dsh-client-connection":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-sidebar":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6","@deepseek-ai/dsh-client-ui-primitives":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-remote-web-ui_0.2.5_1786889490637_0.633055546002883","host":"s3://npm-registry-packages-npm-production"},"deprecated":"removed: mobile remote-control plugin no longer used; LAN access now goes through the desktop GUI directly"}},"time":{"created":"2026-08-14T15:18:34.493Z","modified":"2026-08-16T17:05:20.247Z","0.1.12":"2026-08-14T15:18:34.825Z","0.2.0":"2026-08-14T18:09:36.972Z","0.2.1":"2026-08-14T18:26:39.566Z","0.2.2":"2026-08-15T07:19:05.716Z","0.2.3":"2026-08-15T11:58:26.110Z","0.2.4":"2026-08-15T20:08:04.855Z","0.2.5":"2026-08-16T14:11:30.844Z"},"license":"Apache-2.0","repository":{"url":"https://github.com/CAPTAIN1275/dsh-ui-web.git","type":"git"},"description":"Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions","maintainers":[{"name":"captain1275","email":"captain1275@163.com"}],"readme":"# DSH Remote Web UI\n\n> 移动端远程控制 + 一键远程更新：扫码配对后用手机远程使用当前 dsh web 工作区；\n> 点击侧边栏更新按钮自动检查并更新 dsh-web-ui 全家桶。\n\nThis repository is an external plugin package for DeepSeek Harness (DSH):\nscan-to-pair mobile remote control for the dsh web GUI, plus a one-click\nself-update for the dsh-web-ui family. It is a single dual-face package — the\nhost half owns pairing tokens, device sessions, the `/api/pair` route family,\nand the `/api/update` surface; the browser half renders the sidebar-foot\nentries (the download trigger and the phone icon beside the settings button),\nthe pairing panel with a QR code, live device status, and stop/refresh/copy\nactions, and the update panel that probes and runs the update.\n\n## What it does\n\n- **Entry**: a phone icon in the sidebar foot, next to the settings button.\n- **Panel**: \"移动端远程控制\" title, \"扫码或在手机上打开链接，即可远程控制当前工作区\"\n  subtitle, a \"手机扫码连接\" card with the status area (\"等待手机连接\" + status\n  badge), a large QR code, the \"无法扫码？可以在手机上打开链接\" hint, and three\n  buttons: 停止 / 刷新二维码 / 复制链接.\n- **Phone side**: scanning the QR binds the phone with a one-time,\n  time-limited token and lands it on the **standalone mobile surface at\n  `/m`** — a thin client purpose-built for a small screen (see\n  [Screenshots](#screenshots)), not the desktop UI squeezed into a phone.\n  The link carries a `workspace` parameter so the phone lands in the same\n  workspace the desktop was looking at.\n- **Security**: one active one-time token (a refresh invalidates the old\n  link; an accepted token cannot be reused; tokens expire). 停止 revokes\n  every paired device and the current token — paired devices are cut off on\n  their next request. When the plugin's `requirePairingForLan` gate is on\n  (default), every non-loopback `/api` request must carry a live paired\n  device cookie, so the QR is the only way into a LAN-exposed dsh web.\n- **Live status**: the desktop panel mirrors the pairing state in real time\n  (waiting → connected → disconnected) over an SSE stream.\n- **Remote update**: the download trigger in the sidebar foot (left of the\n  phone icon) opens the update panel, which probes the npm registry for the\n  installed `@captain1275/dsh-*` family releases. When a newer release exists\n  the panel runs the update automatically (`pnpm update` inside the owning\n  dsh profile; the loopback-only `/api/update/status` + `/api/update/run`\n  endpoints drive it) and asks for a dsh web restart to pick it up. Local\n  link installs (development mode) are detected and report the npm state\n  without updating.\n\n## Screenshots\n\nThe phone surface on a 390pt viewport. Light is the default theme; a\nsun/moon toggle in every header flips to the dark palette at any time.\n\n- **Workspaces** — the roster, each row a workspace with its own sessions:\n  ![Workspaces](docs/screenshots/mobile-workspaces.png)\n- **Sessions** — one workspace's sessions, headed by the 新建会话 button\n  (creates a blank session attached to the workspace and opens it\n  immediately):\n  ![Sessions](docs/screenshots/mobile-sessions.png)\n- **Chat** — messages with the desktop fold discipline (collapsed\n  深度思考 reasoning and 工具 tool-call rows), a pinned composer with\n  模型 / 权限 chips, and a live stream while the agent works:\n  ![Chat](docs/screenshots/mobile-chat.png)\n- **Model picker** — the bottom sheet with a provider-grouped catalog and a\n  思考强度 section per model (the same `session.models` directory the\n  desktop uses):\n  ![Model sheet](docs/screenshots/mobile-model-sheet.png)\n\n## Requirements\n\n- A DSH installation whose `dsh` CLI supports profiles (`dsh --profile`,\n  `dsh plugin`) — the profile/bundle mechanism this package rides on.\n- For LAN use the server must be reachable from the phone: start with\n  `dsh web --host 0.0.0.0`. With the default `127.0.0.1` bind the panel\n  shows an explicit explanation instead of a dead QR code — unless a public\n  base URL is configured (see \"Remote access over the internet\" below),\n  which makes the QR reachable from anywhere without rebinding. The panel's\n  mint/stop endpoints are loopback-only by design: a desktop browser\n  opened at the LAN URL sees a \"配对面板仅限本机使用\" banner instead —\n  open the panel at `http://127.0.0.1` and let the phone use the paired\n  link.\n- For the one-click public tunnel (`autoTunnel`), the `cloudflared`\n  platform binary ships with the package (its postinstall downloads it; a\n  runtime download covers installers that skip postinstall scripts). No\n  user-side tooling, account, or domain is needed — a Cloudflare quick\n  tunnel is free and anonymous.\n\n## Install\n\nInstall the family aggregate package `@captain1275/dsh-web-ui-all` (all plugins and skins in one) or this plugin alone:\n\n```sh\n# Recommended: install directly from npm\ndsh plugin --profile web add @captain1275/dsh-remote-web-ui\n\n# Or from the repository (development loop)\ngit clone https://github.com/zhu1090093659/dsh-web-ui.git\ncd dsh-web-ui\npnpm install && pnpm -r build\ndsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui\n\n```\n\nRestart the profile (`dsh web`), then open the phone icon in the sidebar\nfoot. The plugin's `cordis.patch.yml` inserts the single plugin row that\nmounts both halves.\n\n> `github:<org>/<repo>` installs work for a standalone repo whose package\n> sits at the root (the `prepare` script builds `lib/` during install;\n> pnpm ≥10 blocks that until you copy the printed key into the profile's\n> `pnpm-workspace.yaml` `allowBuilds` and re-run). Monorepo subpackages\n> use the `link:` form above.\n\n## Use\n\n1. `dsh web --host 0.0.0.0` (the printed LAN URL confirms reachability).\n2. Click the phone icon → the panel mints a fresh one-time QR.\n3. Scan with the phone (or open the copied link): the phone binds and\n   lands on the **standalone mobile surface at `/m`** — no desktop UI on a\n   small screen. The surface is deliberately thin:\n   - workspaces straight away (a 新建会话 button lives on each workspace's\n     session list: it creates a blank session attached to that workspace via\n     the host's `session.create` and opens the new chat immediately),\n   - one workspace's sessions load **incrementally** (20 rows per page,\n     \"加载更多会话\" continues; never the whole list at once),\n   - opening a session fetches its chat content **on demand** (history\n     pages, \"加载更早的消息\" goes further back),\n   - a live stream shows new messages as they arrive, with a prompt box\n     for sending your own,\n   - a **light-first theme**: the surface ships a light palette by default;\n     a sun/moon toggle in every header flips to the dark palette and the\n     choice persists across visits (localStorage),\n   - messages render with the desktop fold discipline: reasoning hides\n     behind a collapsed 深度思考 disclosure, tool calls behind a collapsed\n     工具 row (tap to see each call's arguments), very long answers behind\n     an explicit 展开全文 toggle, and each row carries its time — and\n   - a composer toolbar carries the **model** picker (provider-grouped\n     catalog with a 思考强度 effort section per model) and the **权限**\n     picker (permission presets; 完全权限 requires an explicit confirm\n     step). Both ride the host's own `session.models` /\n     `session.selectModel` RPCs and the `/permission` command — the phone\n     changes the same session settings the desktop would.\n4. The desktop badge flips to 已连接 in real time; it falls back to\n   offline/断开 when the phone leaves.\n5. 刷新二维码 invalidates the old link and issues a new one. 停止 revokes\n   mobile access: paired devices 403 on their next request, including their\n   live stream.\n\nThe mobile surface is fully self-contained in this plugin: the `/m` page\nand its data channel (`/m/api`) are served by the plugin's own routes and\nneed **no harness source changes** — the phone's RPC calls ride the\nplugin's `/m/api` proxy (which delegates to the host ApiProxy service and\npages `session.list` itself), so the tunneled Host never has to enter the\nconnection plugin's trust fence. The phone is gated by its paired-device\ncookie and an explicit method allowlist (settings/credentials/host-action\ndomains are never reachable from the phone; model reads/writes are limited\nto the advisory `session.models` / `session.selectModel` pair, creation to\n`session.create` (workspace id only — the phone never names a working\ndirectory of its own), and the permission picker only ever sends the\nmode-agnostic `/permission` command\nthrough the already-allowlisted `session.prompt`); the live stream arrives\nover Server-Sent Events on `/m/api/events.mux`.\n\n### Behavior notes\n\n- Installing this plugin gates non-loopback `/api` access behind pairing\n  (see `requirePairingForLan` in `src/index.ts`). A desktop browser opened\n  via the LAN URL must pair like any remote device; loopback (127.0.0.1)\n  is unaffected. Set `requirePairingForLan: false` in the profile patch to\n  restore the open-LAN behavior while keeping tokens/status/revocation.\n- The QR link is built from the machine's non-internal IPv4 literals; a\n  multi-homed host (Wi-Fi + wired, or a proxy/VPN virtual adapter) shows a\n  radio picker so you can advertise the network the phone can actually\n  reach. The first literal is the default. When `publicBaseUrl` is set, the\n  picker adds a 公网地址 option on top — the default QR then uses the\n  public base, and picking a LAN literal re-mints an in-network link.\n- A configured `publicBaseUrl` satisfies the reachable-bind requirement on\n  its own: `dsh web` bound to `127.0.0.1` (no `--host 0.0.0.0`) still mints\n  working public QR links through the tunnel.\n\n## Remote access over the internet (tunnels)\n\n### One-click public tunnel (recommended)\n\nTurn on `autoTunnel` in the plugin settings card (or set\n`autoTunnel: true` in the profile patch). The plugin then runs its own\nCloudflare quick tunnel — the `cloudflared` binary ships with the package,\nno install, account, or domain needed — and wires everything itself:\n\n- the minted `https://xxx.trycloudflare.com` URL becomes the QR base, so a\n  phone anywhere can pair. The panel shows the tunnel status (starting /\n  running / failed with the reason), and a crash is restarted\n  automatically with backoff.\n\nThe QR stays LAN-only until the tunnel reports its URL, and a tunnel\nrestart mints a NEW hostname — the plugin clears the old link and mints a\nfresh one, so users never touch configuration. Note that a quick tunnel is\npublic: anyone with the URL can load the static page; the pairing gate is\nthe real fence, and the phone's data channel (`/m/api`) is protected by\nits own paired-device gate plus a method allowlist — the tunneled Host\nnever needs to enter the connection plugin's trust fence, so **no profile\nor harness customization is required for the auto tunnel to work**.\n\n### Manual tunnels (bring your own)\n\nThe QR link is normally a LAN URL, so a phone outside the house cannot use\nit. Point a tunnel at the dsh web port and tell the plugin its public\naddress — the QR is then built from the tunnel URL and the phone-facing\npairing fence trusts the tunneled host. Two knobs are involved:\n\n- **`publicBaseUrl`** (plugin config, in the profile patch or the settings\n  card): the public origin, e.g. `https://foo.trycloudflare.com`. The QR\n  link is built from it, and `accept`/`heartbeat`/`status` accept its host.\n  Malformed values are ignored with a warning (LAN-only behavior kept).\n- **`--trusted-host <authority>`** (dsh web flag): the transport-level\n  `/api` fence of the connection plugin must accept the public host too —\n  without it every `/api` request through the tunnel 403s *before* the\n  pairing layer (the plugin's own fence only covers the `/api/pair`\n  routes). Pass the public host (or `host:port`) exactly as the tunnel\n  forwards it.\n\n### Cloudflare Tunnel (quick tunnel — no account, no domain)\n\nInstall the client once (macOS: `brew install cloudflared`; other systems:\ngrab the `cloudflared-darwin-{arm64,amd64}` binary from the official GitHub\nreleases). Then:\n\n```sh\n# 1. Expose the local port (whatever dsh web is listening on):\ncloudflared tunnel --url http://127.0.0.1:3080\n#    prints something like: https://xxxx-xxxx-xxxx.trycloudflare.com\n\n# 2. Start dsh web with that host trusted (use --host 0.0.0.0 too when LAN\n#    access should stay available):\ndsh web --trusted-host xxxx-xxxx-xxxx.trycloudflare.com\n```\n\nThen set `publicBaseUrl: https://xxxx-xxxx-xxxx.trycloudflare.com` in the\nprofile patch (or the plugin settings card — it hot-reloads). Open the\nphone icon at `http://127.0.0.1`, scan the QR from anywhere: the phone\nbinds, reloads into the mobile surface, and heartbeats keep it online.\n\nNotes:\n\n- Quick tunnels are free and need no login, but the hostname is random per\n  run: every `cloudflared` restart changes it, so update `--trusted-host`\n  and `publicBaseUrl` together. Cloudflare documents no uptime guarantees;\n  in-flight-request concurrency is capped (HTTP 429 past it), and **Quick\n  Tunnels do not forward Server-Sent Events**. `Tailscale Serve` (and\n  `tailscale serve` on a single port) behaves the same way. SSE is how the\n  phone receives **live messages** in real time, so over a quick tunnel or\n  Tailscale Serve the mobile chat falls back to polling: the phone still\n  sends and receives messages (everything else rides plain HTTP, which does\n  forward), only a new message may arrive a few seconds late instead of\n  instantly. The plugin polls `session.history` on a short interval once the\n  SSE channel goes silent, and resumes streaming as soon as SSE works again.\n  For true real-time push, point the QR at a tunnel that forwards SSE — a\n  Cloudflare **named tunnel** (domain hosted on Cloudflare, see below), or a\n  plain TCP port forward (LAN address, the `tailscale up` virtual-interface\n  address, or a manual `ssh -L` / cloudflared TCP tunnel to the port).\n- A quick tunnel is public: anyone with the URL can load the static page.\n  The pairing gate is the real fence — unpaired devices get 403 on every\n  `/api` call — so keep `requirePairingForLan` on.\n- For a stable hostname, create a named tunnel from the Cloudflare\n  dashboard (Networking → Tunnels; the domain must be hosted on\n  Cloudflare) and use its hostname in the same two places. Reachability\n  from mainland China is not guaranteed by Cloudflare; verify locally.\n- Tailscale is an alternative for personal use that needs no plugin\n  changes at all: its virtual-interface address (`100.x.y.z`) shows up in\n  the QR's address picker automatically, and a phone on the same tailnet\n  reaches it like a LAN host.\n\n## Development\n\nWork from this repository (no sibling checkout needed):\n\n```sh\ncd ~/code/dsh-web-ui\nexport NPM_TOKEN='<token>'   # only if private @deepseek-ai auth is still required\npnpm install\npnpm --filter @captain1275/dsh-remote-web-ui run build\npnpm --filter @captain1275/dsh-remote-web-ui test\npnpm --filter @captain1275/dsh-remote-web-ui run typecheck\n```\n\nThe peer APIs come from the official NPM SDK: every `@deepseek-ai/*` package\nused here is declared in devDependencies (rc.6), and TypeScript/Vitest resolve\ntypes straight from node_modules — no DSH source checkout is required. The\nconsumer-side `prepare` build (`tsdown.prepare.config.ts`) transpiles without\ntype checking, so git installs work without any harness checkout either.\n\n## Checks\n\n```sh\npnpm run typecheck\npnpm test\npnpm run build\n```\n\n## Harness contract dependencies\n\nThis plugin rides three harness seams that may not exist in older checkouts:\n\n- **`api/gate` waterfall** (packages/client/connection): the /api route and\n  event WebSocket upgrades emit this event after the trust fence so plugins\n  can enforce application-level access control. Without it, revocation has\n  no server-side teeth.\n- **`sidebar.remote` foot seat** (packages/client/ui-sidebar): the sidebar\n  declares and renders the seat the phone entry occupies.\n- **LAN runtime connection fixes** (host-apiproxy `mintRpcId` fallback for\n  insecure-context origins; the 20260808-branch connection loop opening the\n  host stream after the mux stream): without them the browser runtime cannot\n  run on a plain-HTTP LAN page at all (the mobile side of this feature).\n\nThe fence helpers (`isTrustedApiRequest` / `isLoopbackHostname`) are\nreimplemented locally in `src/gate.ts` / `src/routes.ts`: the 20260810\nupstream moved the trust fence inside the connection plugin and stopped\nexporting them, so the pairing routes carry their own copy scoped to the\nliterals the QR links advertise.\nSee the Agent Notes `api-gate-and-sidebar-remote-seat` and\n`lan-runtime-connection-fixes` in the harness checkout.\n\n## Manual E2E: LAN pairing round trip\n\nThe unit/component specs cover the route family, the gate, and the panel,\nbut the pairing loop involves a real browser on a non-loopback origin.\nRepeat this after any change to the wire contract or the connection loop:\n\n1. Start the server on all interfaces with a test workspace root:\n   `dsh web --host 0.0.0.0 --port 3190 --workspace-root /tmp/remote-e2e`.\n2. Open the **loopback** URL (`http://127.0.0.1:3190`) in a browser: the\n   phone icon sits in the sidebar foot; the panel mints a QR instantly.\n3. In a second tab (or a phone) open the **LAN** URL with the pair token\n   (e.g. `http://192.168.1.7:3190/?pair=<token>`): the page accepts, sets\n   the HttpOnly `dsh_pair` cookie, reloads, and boots the full UI — no\n   console errors, and a generation round trip completes.\n4. The desktop badge flips to 已连接 in real time; a LAN-origin desktop\n   page instead shows the 配对面板仅限本机使用 banner and opens no status\n   stream.\n5. 停止 on the desktop cuts the phone off: its next `/api` request 403s\n   (reconnect loops retry until a fresh QR re-pairs).\n\nThe public path is the same round trip through a tunnel (see \"Remote access\nover the internet\"): loopback mint → phone opens the public QR URL →\naccept → full UI. Both `publicBaseUrl` (plugin config) and\n`--trusted-host` (dsh web flag) must name the tunneled host; the desktop\npanel still opens at `http://127.0.0.1`.\n\n## Known Limitations and Deferred Work\n\n- **Revocation is per-request**: a paired phone whose request is already in\n  flight when 停止 lands completes that request; the next one 403s.\n- **Device sessions are in-memory**: pairing state (token + devices) resets\n  with the `dsh web` process.\n- **No per-device management UI**: the panel shows aggregate status\n  (waiting / connected N / offline); individual device revocation is\n  deferred.\n- **Quick-tunnel hostnames change per run**: a `trycloudflare.com` URL is\n  random on every `cloudflared` start, so `--trusted-host` and\n  `publicBaseUrl` must be updated together whenever the tunnel restarts.\n  A named tunnel (fixed hostname) avoids the churn.\n- **Dev HMR**: `dsh web --dev` polls every roster bundle by path, so\n  rebuilding this package (its own `tsdown --watch`) hot-reloads the client\n  bundle; no harness-side watcher is involved.\n\n## Dependency rationale\n\n`qrcode.react` (MIT, actively maintained, React 16–19 support) renders the\nQR as a dependency-free SVG component — no canvas, no server-side image\ngeneration. It is inlined into the client bundle at build time (like the\nofficial skin/turtle-ui plugins inline their non-shared deps), so profile\ninstallations need no extra runtime dependency beyond the dsh peer closure.\n`schemastery` is the DSH-standard config schema validator.","readmeFilename":""}