dsh-web-remote 0.0.0 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Liming Xie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/client.js ADDED
@@ -0,0 +1,330 @@
1
+ /**
2
+ * dsh-web-remote — client half (browser settings section).
3
+ *
4
+ * module-loader factory format (mirrors dsh-client-ui-settings-plugins).
5
+ * Registers a "settings.section" slot rendering the Remote Access panel:
6
+ * status point + join form (hub/token/name) + Connect/Disconnect/Revoke.
7
+ * Talks to the server half over the `/remote-access` RPC channel.
8
+ *
9
+ * i18n: registers zh/en dictionaries under NS and reads via ctx.locale.bind(NS);
10
+ * falls back to English when the locale service is absent.
11
+ */
12
+ window.__ModuleLoader__.load({
13
+ id: "dsh-web-remote",
14
+ factory: (require) => {
15
+ const React = require("react");
16
+
17
+ const inject = ["connection", "slots", "locale"];
18
+ const NS = "settings.remote-access";
19
+
20
+ const zh = {
21
+ nav: "远程访问",
22
+ status_unconfigured: "未接入",
23
+ status_disconnected: "未接入(已断开,配置保留)",
24
+ status_connecting: "连接中…",
25
+ status_connected: "已连接",
26
+ status_reconnecting: "断线重连…",
27
+ status_external: "已接入(由 rdsh CLI / 服务托管)",
28
+ hubUrl: "Hub 地址",
29
+ joinToken: "授权令牌",
30
+ name: "主机名",
31
+ hubPlaceholder: "https://hub.example.com",
32
+ tokenPlaceholder: "一次性授权令牌",
33
+ namePlaceholder: "my-mac",
34
+ connect: "接入",
35
+ disconnect: "断开",
36
+ revoke: "注销",
37
+ confirmOverwrite: "将覆盖现有 host 配置,再次点击接入确认",
38
+ tip_unconfigured: "在 hub 门户「添加主机」获取授权令牌,粘贴到下方后点击接入。",
39
+ tip_connecting: "正在注册并建立隧道…",
40
+ tip_connected_pre: "现在可从任何地方,使用浏览器访问:",
41
+ tip_connected_post: "登录 hub 门户,在主机列表中,找到这台主机,即可访问它。",
42
+ tip_reconnecting: "隧道断开,正在自动重连,无需操作。",
43
+ tip_disconnected: "已断开,配置与授权已保留,点击接入即可恢复。",
44
+ tip_external: "该主机由 rdsh CLI / 服务托管,请用 rdsh 命令管理。",
45
+ };
46
+
47
+ const en = {
48
+ nav: "Remote Access",
49
+ status_unconfigured: "Not joined",
50
+ status_disconnected: "Disconnected (config kept)",
51
+ status_connecting: "Connecting…",
52
+ status_connected: "Connected",
53
+ status_reconnecting: "Reconnecting…",
54
+ status_external: "Managed by rdsh CLI/service",
55
+ hubUrl: "Hub URL",
56
+ joinToken: "Auth Token",
57
+ name: "Name",
58
+ hubPlaceholder: "https://hub.example.com",
59
+ tokenPlaceholder: "One-time auth token",
60
+ namePlaceholder: "my-mac",
61
+ connect: "Connect",
62
+ disconnect: "Disconnect",
63
+ revoke: "Revoke",
64
+ confirmOverwrite: "Will overwrite existing host config — click Connect again to confirm",
65
+ tip_unconfigured: "Get an auth token from the hub portal (Add host), paste it below, then click Connect.",
66
+ tip_connecting: "Registering and establishing the tunnel…",
67
+ tip_connected_pre: "You can now access it from anywhere via your browser:",
68
+ tip_connected_post: "Sign in to the hub portal, find this host in the host list, and open it.",
69
+ tip_reconnecting: "The tunnel dropped; it is reconnecting automatically — no action needed.",
70
+ tip_disconnected: "Disconnected — config and auth are kept. Click Connect to resume.",
71
+ tip_external: "This host is managed by the rdsh CLI/service; manage it with rdsh commands.",
72
+ };
73
+
74
+ const CSS = `
75
+ .dsh-web-remote{display:flex;flex-direction:column;gap:12px;max-width:560px}
76
+ .dsh-web-remote-status{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:1.5}
77
+ .dsh-web-remote-dot{width:8px;height:8px;border-radius:9999px;flex:0 0 auto;background:var(--dsw-alias-label-tertiary)}
78
+ .dsh-web-remote-dot.connecting{background:var(--dsw-alias-state-warn-primary);animation:dwr-pulse 1s ease-in-out infinite}
79
+ .dsh-web-remote-dot.connected{background:var(--dsw-alias-state-success-primary)}
80
+ .dsh-web-remote-dot.reconnecting{background:var(--dsw-alias-state-error-primary);animation:dwr-pulse 1s ease-in-out infinite}
81
+ @keyframes dwr-pulse{0%,100%{opacity:1}50%{opacity:.35}}
82
+ .dsh-web-remote-msg{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:1.5}
83
+ .dsh-web-remote-tip{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
84
+ .dsh-web-remote-tip-url{color:var(--dsw-alias-brand-primary);word-break:break-all;text-decoration:none}
85
+ .dsh-web-remote-tip-url:hover{text-decoration:underline}
86
+ .dsh-web-remote-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}
87
+ .dsh-web-remote-field+.dsh-web-remote-field{border-top:1px solid var(--dsw-alias-border-l2)}
88
+ .dsh-web-remote-field label{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}
89
+ .dsh-web-remote-field input{width:100%;box-sizing:border-box;height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font:inherit;font-size:13px;line-height:1.5}
90
+ .dsh-web-remote-field input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
91
+ .dsh-web-remote-field input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
92
+ .dsh-web-remote-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
93
+ .dsh-web-remote-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:none;border-radius:18px;cursor:pointer;font:inherit;font-size:14px;line-height:22px;padding:0 14px;color:var(--dsw-alias-label-primary);background:transparent;box-shadow:inset 0 0 0 1px var(--dsw-alias-button-ghost-active-border)}
94
+ .dsh-web-remote-btn:hover:not(:disabled){background:var(--dsw-alias-button-ghost-active-fill)}
95
+ .dsh-web-remote-btn:disabled{cursor:not-allowed;opacity:.4}
96
+ .dsh-web-remote-btn-primary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);box-shadow:none}
97
+ .dsh-web-remote-btn-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}
98
+ .dsh-web-remote-btn-danger{color:var(--dsw-alias-state-error-primary);box-shadow:inset 0 0 0 1px var(--dsw-alias-state-error-primary)}
99
+ .dsh-web-remote-btn-danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
100
+ `;
101
+
102
+ let styleInjected = false;
103
+ function injectStyle() {
104
+ if (styleInjected || typeof document === "undefined") return;
105
+ styleInjected = true;
106
+ const el = document.createElement("style");
107
+ el.setAttribute("data-plugin-css", "dsh-web-remote");
108
+ el.textContent = CSS;
109
+ document.head.appendChild(el);
110
+ }
111
+
112
+ function Field({ label, value, disabled, placeholder, onChange, onFocus }) {
113
+ return React.createElement(
114
+ "div",
115
+ { className: "dsh-web-remote-field" },
116
+ React.createElement("label", null, label),
117
+ React.createElement("input", {
118
+ value,
119
+ disabled,
120
+ placeholder,
121
+ onChange: (e) => onChange(e.target.value),
122
+ onFocus,
123
+ }),
124
+ );
125
+ }
126
+
127
+ function Panel({ rpc, t }) {
128
+ const [status, setStatus] = React.useState("unconfigured");
129
+ const [hub, setHub] = React.useState("");
130
+ const [name, setName] = React.useState("");
131
+ const [token, setToken] = React.useState("");
132
+ const [message, setMessage] = React.useState(undefined);
133
+ const [busy, setBusy] = React.useState(false);
134
+ const [confirmOverwrite, setConfirmOverwrite] = React.useState(false);
135
+ const [savedToken, setSavedToken] = React.useState(false);
136
+
137
+ React.useEffect(() => {
138
+ let alive = true;
139
+ const tick = async () => {
140
+ try {
141
+ const res = await rpc.call("/remote-access", "state", { args: {} });
142
+ if (!alive || !res.ok) return;
143
+ const v = res.value || {};
144
+ setStatus(v.status || "unconfigured");
145
+ if (typeof v.hub === "string") setHub(v.hub);
146
+ if (typeof v.name === "string") setName(v.name);
147
+ if (typeof v.message === "string" && v.message !== "") setMessage(v.message);
148
+ setSavedToken(v.hasToken === true);
149
+ } catch {
150
+ /* 瞬时错误忽略,下一轮重试 */
151
+ }
152
+ };
153
+ void tick();
154
+ const id = setInterval(() => void tick(), 1000);
155
+ return () => {
156
+ alive = false;
157
+ clearInterval(id);
158
+ };
159
+ }, [rpc]);
160
+
161
+ const external = status === "external";
162
+ const showForm = status === "unconfigured" || status === "disconnected";
163
+ const showDisconnect = status === "connecting" || status === "connected" || status === "reconnecting";
164
+ const showRevoke = status === "disconnected" || showDisconnect;
165
+
166
+ const connect = async () => {
167
+ setBusy(true);
168
+ try {
169
+ const res = await rpc.call("/remote-access", "connect", {
170
+ args: { hub, token, name, confirmOverwrite: confirmOverwrite || undefined },
171
+ });
172
+ if (!res.ok) {
173
+ if (res.error && res.error.code === "mode-conflict") {
174
+ setConfirmOverwrite(true);
175
+ setMessage(t("confirmOverwrite"));
176
+ } else {
177
+ setMessage(res.error ? res.error.message : "connect failed");
178
+ }
179
+ } else {
180
+ setToken("");
181
+ setConfirmOverwrite(false);
182
+ setMessage(undefined);
183
+ }
184
+ } finally {
185
+ setBusy(false);
186
+ }
187
+ };
188
+
189
+ const disconnect = async () => {
190
+ setBusy(true);
191
+ try {
192
+ await rpc.call("/remote-access", "disconnect", { args: {} });
193
+ } finally {
194
+ setBusy(false);
195
+ }
196
+ };
197
+
198
+ const revoke = async () => {
199
+ setBusy(true);
200
+ try {
201
+ await rpc.call("/remote-access", "revoke", { args: {} });
202
+ setHub("");
203
+ setName("");
204
+ } finally {
205
+ setBusy(false);
206
+ }
207
+ };
208
+
209
+ const disabled = busy || external;
210
+ // 未接入态需要令牌;断开态可留空(复用已保存授权)
211
+ const canConnect = !disabled && hub.trim() !== "" && (status !== "unconfigured" || token.trim() !== "");
212
+
213
+ const statusLine = React.createElement(
214
+ "div",
215
+ { className: "dsh-web-remote-status" },
216
+ React.createElement("span", { className: "dsh-web-remote-dot " + status }),
217
+ React.createElement("span", null, t("status_" + status)),
218
+ );
219
+
220
+ const form = showForm
221
+ ? React.createElement(
222
+ React.Fragment,
223
+ null,
224
+ React.createElement(Field, {
225
+ label: t("hubUrl"),
226
+ value: hub,
227
+ disabled,
228
+ placeholder: t("hubPlaceholder"),
229
+ onChange: setHub,
230
+ }),
231
+ React.createElement(Field, {
232
+ label: t("joinToken"),
233
+ value: token !== "" ? token : savedToken ? "••••••••" : "",
234
+ disabled,
235
+ placeholder: t("tokenPlaceholder"),
236
+ onFocus: (e) => {
237
+ if (token === "" && savedToken) e.target.select();
238
+ },
239
+ onChange: setToken,
240
+ }),
241
+ React.createElement(Field, {
242
+ label: t("name"),
243
+ value: name,
244
+ disabled,
245
+ placeholder: t("namePlaceholder"),
246
+ onChange: setName,
247
+ }),
248
+ )
249
+ : null;
250
+
251
+ const actions = external
252
+ ? null
253
+ : React.createElement(
254
+ "div",
255
+ { className: "dsh-web-remote-actions" },
256
+ showForm
257
+ ? React.createElement(
258
+ "button",
259
+ { className: "dsh-web-remote-btn dsh-web-remote-btn-primary", disabled: !canConnect, onClick: connect },
260
+ busy ? "…" : t("connect"),
261
+ )
262
+ : null,
263
+ showDisconnect
264
+ ? React.createElement(
265
+ "button",
266
+ { className: "dsh-web-remote-btn", disabled, onClick: disconnect },
267
+ t("disconnect"),
268
+ )
269
+ : null,
270
+ showRevoke
271
+ ? React.createElement(
272
+ "button",
273
+ { className: "dsh-web-remote-btn dsh-web-remote-btn-danger", disabled, onClick: revoke },
274
+ t("revoke"),
275
+ )
276
+ : null,
277
+ );
278
+
279
+ const tip =
280
+ status === "connected"
281
+ ? React.createElement(
282
+ "p",
283
+ { className: "dsh-web-remote-tip" },
284
+ t("tip_connected_pre"),
285
+ " ",
286
+ React.createElement(
287
+ "a",
288
+ { className: "dsh-web-remote-tip-url", href: hub, target: "_blank", rel: "noreferrer" },
289
+ hub,
290
+ ),
291
+ " ",
292
+ t("tip_connected_post"),
293
+ )
294
+ : React.createElement("p", { className: "dsh-web-remote-tip" }, t("tip_" + status));
295
+
296
+ return React.createElement(
297
+ "div",
298
+ { className: "dsh-web-remote" },
299
+ statusLine,
300
+ tip,
301
+ message ? React.createElement("p", { className: "dsh-web-remote-msg" }, message) : null,
302
+ form,
303
+ actions,
304
+ );
305
+ }
306
+
307
+ function apply(ctx) {
308
+ injectStyle();
309
+ const locale = ctx.locale;
310
+ const t = locale && typeof locale.bind === "function" ? locale.bind(NS) : (k) => en[k] ?? k;
311
+ if (locale && typeof locale.register === "function") {
312
+ ctx.effect(() => locale.register(NS, { zh, en }), "dsh-web-remote: locale");
313
+ }
314
+ ctx.slots.inject("settings.section", () =>
315
+ ctx.slots.register(
316
+ {
317
+ name: "settings.section",
318
+ id: "remote-access",
319
+ order: 99,
320
+ label: () => t("nav"),
321
+ inject: () => ({ rpc: ctx.get("connection").rpc, t }),
322
+ },
323
+ Panel,
324
+ ),
325
+ );
326
+ }
327
+
328
+ return { apply, inject };
329
+ },
330
+ });
@@ -0,0 +1,6 @@
1
+ # dsh-web-remote server bundle: mounts this package as a loader row.
2
+ # That row is BOTH the server plugin (node half) AND the marker that lets
3
+ # dsh-client-modules discover the dsh.client browser half.
4
+ - insert:
5
+ - id: remote-access
6
+ name: 'dsh-web-remote'
@@ -0,0 +1,32 @@
1
+ /** 面板状态(client 半 §2 五态 + 断开后态) */
2
+ export type Status = "unconfigured" | "disconnected" | "connecting" | "connected" | "reconnecting" | "external";
3
+ type RpcResult = {
4
+ ok: true;
5
+ value: unknown;
6
+ } | {
7
+ ok: false;
8
+ error: {
9
+ code: string;
10
+ message: string;
11
+ details?: unknown;
12
+ };
13
+ };
14
+ interface RpcHandler {
15
+ handle(channel: string, handler: (endpoint: string, payload: {
16
+ args?: Record<string, unknown>;
17
+ }, signal: AbortSignal) => Promise<RpcResult>, options?: {
18
+ authority?: string;
19
+ }): void;
20
+ }
21
+ interface Ctx {
22
+ connection: {
23
+ rpc: RpcHandler;
24
+ };
25
+ webServer: {
26
+ port: number;
27
+ };
28
+ on(event: string, cb: () => void): void;
29
+ }
30
+ export declare const inject: string[];
31
+ export declare function apply(ctx: Ctx): void;
32
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * dsh-web-remote — server half (Cordis function plugin, runs in the dsh web process).
3
+ *
4
+ * Runs the join tunnel in-process (no spawn), forwarding to the local dsh web
5
+ * (`127.0.0.1:<webServer.port>`), and exposes the `/remote-access` RPC channel
6
+ * (`connect` / `disconnect` / `revoke` / `state`) to the browser client half.
7
+ *
8
+ * Function-plugin form (export `inject` + `apply`) — no `@deepseek-ai/cordis`
9
+ * runtime import, only a minimal local `Ctx` type.
10
+ */
11
+ import { existsSync } from "node:fs";
12
+ import { registerJoin, startJoin, selfRevoke, clearPersistedToken, readPersistedToken, readJoinLock, loadConfig, saveConfig, DEFAULT_HOST_CONFIG_PATH, } from "rdsh-gateway";
13
+ const ok = (value) => ({ ok: true, value });
14
+ const err = (code, message) => ({ ok: false, error: { code, message, details: {} } });
15
+ function mapState(s) {
16
+ switch (s) {
17
+ case "connecting":
18
+ return "connecting";
19
+ case "connected":
20
+ return "connected";
21
+ case "reconnecting":
22
+ return "reconnecting";
23
+ case "rejected":
24
+ case "stopped":
25
+ return "disconnected";
26
+ }
27
+ }
28
+ export const inject = ["connection", "webServer"];
29
+ export function apply(ctx) {
30
+ let handle = null;
31
+ let liveState = null; // 仅隧道运行中非 null
32
+ let currentHub;
33
+ let currentName;
34
+ let lastMessage;
35
+ const hooks = {
36
+ onState: (s, detail) => {
37
+ liveState = s;
38
+ if (s === "rejected") {
39
+ // 永久失败(token 吊销)→ 记录原因并停,不重连
40
+ lastMessage = detail?.message ?? "host token rejected by hub";
41
+ handle = null;
42
+ liveState = null;
43
+ }
44
+ else if (s === "stopped") {
45
+ handle = null;
46
+ liveState = null;
47
+ }
48
+ else if (s === "connected") {
49
+ // 已连接 → 清掉「connecting…」之类的过渡信息
50
+ lastMessage = undefined;
51
+ }
52
+ },
53
+ onLog: (level, message) => {
54
+ // MVP:日志不渲染,仅记录错误供面板 message 展示
55
+ if (level === "error")
56
+ lastMessage = message;
57
+ },
58
+ };
59
+ /** 读 host.json 非 join 模式是否需确认覆盖(D5 档1:host.json 存在且 mode≠join)。 */
60
+ async function needsOverwriteConfirm() {
61
+ if (!existsSync(DEFAULT_HOST_CONFIG_PATH))
62
+ return false;
63
+ const config = await loadConfig(DEFAULT_HOST_CONFIG_PATH);
64
+ return config.mode !== "join";
65
+ }
66
+ async function connect(args) {
67
+ try {
68
+ if (typeof args.hub !== "string" || args.hub.trim() === "") {
69
+ return err("bad-request", "hub (string) required");
70
+ }
71
+ const hub = args.hub.trim();
72
+ if (!/^https?:\/\//.test(hub))
73
+ return err("bad-request", "hub must be an http(s) URL");
74
+ const token = typeof args.token === "string" && args.token.trim() !== "" ? args.token.trim() : undefined;
75
+ const name = typeof args.name === "string" && args.name.trim() !== "" ? args.name.trim() : undefined;
76
+ // 已在跑 → 幂等返回当前态
77
+ if (handle !== null) {
78
+ return ok({ status: mapState(liveState ?? "connected"), hub: currentHub, name: currentName });
79
+ }
80
+ // D5 档2:CLI 持有隧道 → 拒绝
81
+ const held = readJoinLock();
82
+ if (held !== null && held.role === "cli") {
83
+ return err("lock-busy", `join tunnel is owned by the rdsh CLI (pid ${held.pid}); stop it first`);
84
+ }
85
+ // D5 档1:覆盖 lan/cloud 配置需显式确认
86
+ if ((await needsOverwriteConfirm()) && args.confirmOverwrite !== true) {
87
+ return err("mode-conflict", "host.json is not in join mode; re-send with confirmOverwrite: true to overwrite");
88
+ }
89
+ // 注册(join token → host token)或复用持久化 host token;留空 = 复用
90
+ const { token: hostToken, insecure } = await registerJoin({ hubUrl: hub, token, name });
91
+ // 写 host.json(mode join)
92
+ const config = await loadConfig(DEFAULT_HOST_CONFIG_PATH);
93
+ config.mode = "join";
94
+ config.hub = hub;
95
+ config.name = name;
96
+ config.insecure = insecure;
97
+ await saveConfig(DEFAULT_HOST_CONFIG_PATH, config);
98
+ // 起隧道:转发到本进程 dsh
99
+ currentHub = hub;
100
+ currentName = name;
101
+ lastMessage = undefined;
102
+ handle = startJoin({
103
+ hubUrl: hub,
104
+ token: hostToken,
105
+ insecure,
106
+ target: { host: "127.0.0.1", port: ctx.webServer.port },
107
+ role: "plugin",
108
+ hooks,
109
+ });
110
+ return ok({ status: "connecting", hub, name });
111
+ }
112
+ catch (e) {
113
+ return err("register-failed", e instanceof Error ? e.message : String(e));
114
+ }
115
+ }
116
+ async function disconnect() {
117
+ if (handle !== null) {
118
+ await handle.stop();
119
+ handle = null;
120
+ liveState = null;
121
+ }
122
+ return ok({ status: "disconnected", hub: currentHub, name: currentName });
123
+ }
124
+ async function revoke() {
125
+ try {
126
+ if (handle !== null) {
127
+ await handle.stop();
128
+ handle = null;
129
+ liveState = null;
130
+ }
131
+ const config = await loadConfig(DEFAULT_HOST_CONFIG_PATH);
132
+ if (config.mode === "join" && config.hub !== undefined) {
133
+ const token = readPersistedToken(config.hub);
134
+ if (token !== null) {
135
+ await selfRevoke(config.hub, token, config.insecure === true);
136
+ }
137
+ clearPersistedToken(config.hub);
138
+ }
139
+ const fresh = await loadConfig(DEFAULT_HOST_CONFIG_PATH);
140
+ fresh.mode = "lan";
141
+ fresh.hub = undefined;
142
+ fresh.name = undefined;
143
+ fresh.insecure = undefined;
144
+ await saveConfig(DEFAULT_HOST_CONFIG_PATH, fresh);
145
+ currentHub = undefined;
146
+ currentName = undefined;
147
+ lastMessage = undefined;
148
+ return ok({ status: "unconfigured" });
149
+ }
150
+ catch (e) {
151
+ return err("revoke-failed", e instanceof Error ? e.message : String(e));
152
+ }
153
+ }
154
+ async function state() {
155
+ try {
156
+ if (handle !== null && liveState !== null) {
157
+ return ok({ status: mapState(liveState), hub: currentHub, name: currentName, message: lastMessage, hasToken: true });
158
+ }
159
+ const held = readJoinLock();
160
+ if (held !== null && held.role === "cli") {
161
+ return ok({ status: "external" });
162
+ }
163
+ const config = await loadConfig(DEFAULT_HOST_CONFIG_PATH);
164
+ if (config.mode === "join" && config.hub !== undefined) {
165
+ return ok({
166
+ status: "disconnected",
167
+ hub: config.hub,
168
+ name: config.name,
169
+ message: lastMessage,
170
+ hasToken: readPersistedToken(config.hub) !== null,
171
+ });
172
+ }
173
+ return ok({ status: "unconfigured" });
174
+ }
175
+ catch (e) {
176
+ return err("internal", e instanceof Error ? e.message : String(e));
177
+ }
178
+ }
179
+ ctx.connection.rpc.handle("/remote-access", async (endpoint, payload, _signal) => {
180
+ const args = payload?.args ?? {};
181
+ switch (endpoint) {
182
+ case "connect":
183
+ return await connect(args);
184
+ case "disconnect":
185
+ return await disconnect();
186
+ case "revoke":
187
+ return await revoke();
188
+ case "state":
189
+ return await state();
190
+ default:
191
+ return err("bad-request", `unknown endpoint ${endpoint}`);
192
+ }
193
+ }, { authority: "loopback" });
194
+ ctx.on("dispose", () => {
195
+ void handle?.stop();
196
+ });
197
+ }
198
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "dsh-web-remote",
3
- "version": "0.0.0",
4
- "description": "Reserved for remote-dsh (rdsh) DSH plugin for remote access. Placeholder.",
5
- "license": "MIT"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Remote access for DeepSeek Harness: join a rdsh hub from the DSH web UI (plugin, no CLI)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./client": {
15
+ "default": "./client.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "client.js",
22
+ "cordis.patch.yml"
23
+ ],
24
+ "engines": {
25
+ "node": ">=22"
26
+ },
27
+ "dependencies": {
28
+ "rdsh-gateway": "0.4.0"
29
+ },
30
+ "dsh": {
31
+ "bundle": {
32
+ "patch": "./cordis.patch.yml"
33
+ },
34
+ "client": {
35
+ "inject": [
36
+ "@deepseek-ai/dsh-client-connection",
37
+ "@deepseek-ai/dsh-client-runtime",
38
+ "@deepseek-ai/dsh-client-ui-slots",
39
+ "@deepseek-ai/dsh-client-locale"
40
+ ],
41
+ "platform": "web"
42
+ }
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.json"
46
+ }
47
+ }
package/README.md DELETED
@@ -1,4 +0,0 @@
1
- # dsh-web-remote
2
-
3
- Reserved by the [remote-dsh](https://github.com/floatinghotpot/remote-dsh) project
4
- for its DSH plugin (M4). Placeholder — not yet released.