dsh-remote-mobile 1.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 +21 -0
- package/README.md +178 -0
- package/README_EN.md +178 -0
- package/cordis.patch.yml +4 -0
- package/lib/auth/crypto.d.ts +24 -0
- package/lib/auth/crypto.js +144 -0
- package/lib/auth/gate.d.ts +14 -0
- package/lib/auth/gate.js +87 -0
- package/lib/auth/tailscale.d.ts +36 -0
- package/lib/auth/tailscale.js +128 -0
- package/lib/auth/token.d.ts +130 -0
- package/lib/auth/token.js +845 -0
- package/lib/bridge/compat.d.ts +50 -0
- package/lib/bridge/compat.js +191 -0
- package/lib/client.d.ts +5 -0
- package/lib/client.js +3606 -0
- package/lib/index.d.ts +32 -0
- package/lib/index.js +117 -0
- package/lib/routes/api.d.ts +11 -0
- package/lib/routes/api.js +364 -0
- package/lib/routes/login-page.d.ts +4 -0
- package/lib/routes/login-page.js +339 -0
- package/lib/types/auth.d.ts +49 -0
- package/lib/types/auth.js +4 -0
- package/package.json +77 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { getPublicKeyPem } from '../auth/crypto.js';
|
|
2
|
+
/**
|
|
3
|
+
* 手机端授权与登录页面 HTML
|
|
4
|
+
*/
|
|
5
|
+
export function getLoginPageHtml(publicKeyPem, lang = 'zh') {
|
|
6
|
+
const inlinePublicKey = publicKeyPem || getPublicKeyPem();
|
|
7
|
+
const isEn = lang === 'en';
|
|
8
|
+
const i18n = {
|
|
9
|
+
pageTitle: isEn ? 'DSH Remote Access Authorization' : 'DSH 移动端访问授权',
|
|
10
|
+
headerTitle: isEn ? 'Remote Access Auth' : '移动端访问授权',
|
|
11
|
+
headerDesc: isEn ? 'Enter 6-digit dynamic pairing code or persistent password' : '请输入 6 位动态配对码或长期访问密码',
|
|
12
|
+
placeholder: isEn ? '6-digit code or password' : '6 位临时配对码 / 长期密码',
|
|
13
|
+
submitBtn: isEn ? 'Authorize & Connect' : '立即授权连接',
|
|
14
|
+
footerSecurity: isEn ? '🔒 Protected by 2048-bit RSA-OAEP end-to-end encryption' : '🔒 全程受 2048 位 RSA-OAEP 端到端加密保护',
|
|
15
|
+
verifying: isEn ? '🔐 Encrypting & verifying...' : '🔐 正在 RSA 加密与验证...',
|
|
16
|
+
success: isEn ? 'Authorization successful! Entering workspace...' : '授权成功!正在进入工作区...',
|
|
17
|
+
networkErr: isEn ? 'Network connection error, please try again' : '网络连接异常,请检查网络',
|
|
18
|
+
};
|
|
19
|
+
return `<!DOCTYPE html>
|
|
20
|
+
<html lang="${isEn ? 'en' : 'zh-CN'}">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="UTF-8">
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
24
|
+
<title>${i18n.pageTitle}</title>
|
|
25
|
+
<style>
|
|
26
|
+
:root {
|
|
27
|
+
--bg: #0f1117;
|
|
28
|
+
--card-bg: rgba(26, 29, 39, 0.85);
|
|
29
|
+
--card-border: rgba(255, 255, 255, 0.08);
|
|
30
|
+
--primary: #3b82f6;
|
|
31
|
+
--primary-hover: #2563eb;
|
|
32
|
+
--text: #f3f4f6;
|
|
33
|
+
--text-muted: #9ca3af;
|
|
34
|
+
--error: #ef4444;
|
|
35
|
+
--success: #10b981;
|
|
36
|
+
}
|
|
37
|
+
@media (prefers-color-scheme: light) {
|
|
38
|
+
:root {
|
|
39
|
+
--bg: #f3f4f6;
|
|
40
|
+
--card-bg: rgba(255, 255, 255, 0.9);
|
|
41
|
+
--card-border: rgba(0, 0, 0, 0.08);
|
|
42
|
+
--text: #111827;
|
|
43
|
+
--text-muted: #6b7280;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
* { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
|
47
|
+
body {
|
|
48
|
+
background: var(--bg);
|
|
49
|
+
color: var(--text);
|
|
50
|
+
min-height: 100vh;
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
padding: 20px;
|
|
55
|
+
}
|
|
56
|
+
.auth-card {
|
|
57
|
+
width: 100%;
|
|
58
|
+
max-width: 380px;
|
|
59
|
+
background: var(--card-bg);
|
|
60
|
+
backdrop-filter: blur(16px);
|
|
61
|
+
-webkit-backdrop-filter: blur(16px);
|
|
62
|
+
border: 1px solid var(--card-border);
|
|
63
|
+
border-radius: 20px;
|
|
64
|
+
padding: 32px 24px;
|
|
65
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
|
|
66
|
+
text-align: center;
|
|
67
|
+
}
|
|
68
|
+
.logo-badge {
|
|
69
|
+
display: inline-flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
width: 56px;
|
|
73
|
+
height: 56px;
|
|
74
|
+
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
|
75
|
+
border-radius: 16px;
|
|
76
|
+
margin-bottom: 20px;
|
|
77
|
+
color: white;
|
|
78
|
+
box-shadow: 0 8px 16px rgba(59, 130, 246, 0.3);
|
|
79
|
+
}
|
|
80
|
+
h1 {
|
|
81
|
+
font-size: 20px;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
margin-bottom: 8px;
|
|
84
|
+
}
|
|
85
|
+
p.desc {
|
|
86
|
+
font-size: 13px;
|
|
87
|
+
color: var(--text-muted);
|
|
88
|
+
line-height: 1.5;
|
|
89
|
+
margin-bottom: 24px;
|
|
90
|
+
}
|
|
91
|
+
.input-group {
|
|
92
|
+
margin-bottom: 16px;
|
|
93
|
+
}
|
|
94
|
+
input {
|
|
95
|
+
width: 100%;
|
|
96
|
+
height: 48px;
|
|
97
|
+
background: rgba(128, 128, 128, 0.08);
|
|
98
|
+
border: 1px solid var(--card-border);
|
|
99
|
+
border-radius: 12px;
|
|
100
|
+
padding: 0 16px;
|
|
101
|
+
font-size: 16px;
|
|
102
|
+
color: var(--text);
|
|
103
|
+
outline: none;
|
|
104
|
+
text-align: center;
|
|
105
|
+
letter-spacing: 2px;
|
|
106
|
+
transition: all 0.2s;
|
|
107
|
+
}
|
|
108
|
+
input:focus {
|
|
109
|
+
border-color: var(--primary);
|
|
110
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
|
111
|
+
}
|
|
112
|
+
button.submit-btn {
|
|
113
|
+
width: 100%;
|
|
114
|
+
height: 48px;
|
|
115
|
+
background: var(--primary);
|
|
116
|
+
color: white;
|
|
117
|
+
border: none;
|
|
118
|
+
border-radius: 12px;
|
|
119
|
+
font-size: 15px;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
transition: background 0.2s;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
gap: 8px;
|
|
127
|
+
}
|
|
128
|
+
button.submit-btn:active {
|
|
129
|
+
background: var(--primary-hover);
|
|
130
|
+
transform: scale(0.99);
|
|
131
|
+
}
|
|
132
|
+
.status-msg {
|
|
133
|
+
margin-top: 16px;
|
|
134
|
+
font-size: 13px;
|
|
135
|
+
display: none;
|
|
136
|
+
}
|
|
137
|
+
.status-msg.error {
|
|
138
|
+
color: var(--error);
|
|
139
|
+
display: block;
|
|
140
|
+
}
|
|
141
|
+
.status-msg.success {
|
|
142
|
+
color: var(--success);
|
|
143
|
+
display: block;
|
|
144
|
+
}
|
|
145
|
+
.info-footer {
|
|
146
|
+
margin-top: 24px;
|
|
147
|
+
padding-top: 16px;
|
|
148
|
+
border-top: 1px solid var(--card-border);
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
color: var(--text-muted);
|
|
151
|
+
line-height: 1.6;
|
|
152
|
+
text-align: left;
|
|
153
|
+
}
|
|
154
|
+
</style>
|
|
155
|
+
</head>
|
|
156
|
+
<body>
|
|
157
|
+
<div class="auth-card">
|
|
158
|
+
<div class="logo-badge">
|
|
159
|
+
<svg style="width: 28px; height: 28px; fill: currentColor;" viewBox="0 0 24 24">
|
|
160
|
+
<path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"/>
|
|
161
|
+
</svg>
|
|
162
|
+
</div>
|
|
163
|
+
<h1>${i18n.headerTitle}</h1>
|
|
164
|
+
<p class="desc">${i18n.headerDesc}</p>
|
|
165
|
+
|
|
166
|
+
<form id="authForm">
|
|
167
|
+
<div class="input-group">
|
|
168
|
+
<input
|
|
169
|
+
type="password"
|
|
170
|
+
id="credentialInput"
|
|
171
|
+
placeholder="${i18n.placeholder}"
|
|
172
|
+
autocomplete="off"
|
|
173
|
+
required
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
<button type="submit" class="submit-btn" id="submitBtn">
|
|
177
|
+
${i18n.submitBtn}
|
|
178
|
+
</button>
|
|
179
|
+
<div id="statusMsg" class="status-msg"></div>
|
|
180
|
+
</form>
|
|
181
|
+
|
|
182
|
+
<div class="info-footer">
|
|
183
|
+
<div>${i18n.footerSecurity}</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<script>
|
|
188
|
+
const SERVER_RSA_KEY = ${JSON.stringify(inlinePublicKey)};
|
|
189
|
+
const form = document.getElementById('authForm');
|
|
190
|
+
const input = document.getElementById('credentialInput');
|
|
191
|
+
const statusMsg = document.getElementById('statusMsg');
|
|
192
|
+
const submitBtn = document.getElementById('submitBtn');
|
|
193
|
+
|
|
194
|
+
// 解析 SPKI 格式公钥并提取 n 和 e
|
|
195
|
+
function parseSpkiKey(pemStr) {
|
|
196
|
+
const b64 = pemStr.replace(/-----BEGIN PUBLIC KEY-----/g, '').replace(/-----END PUBLIC KEY-----/g, '').replace(/\\s/g, '');
|
|
197
|
+
const binary = window.atob(b64);
|
|
198
|
+
const bytes = new Uint8Array(binary.length);
|
|
199
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
200
|
+
|
|
201
|
+
let idx = 0;
|
|
202
|
+
while (idx < bytes.length && bytes[idx] !== 0x02) idx++;
|
|
203
|
+
const lenByte = bytes[idx + 1];
|
|
204
|
+
let nLen = 0;
|
|
205
|
+
let nStart = 0;
|
|
206
|
+
if (lenByte === 0x82) {
|
|
207
|
+
nLen = (bytes[idx + 2] << 8) | bytes[idx + 3];
|
|
208
|
+
nStart = idx + 4;
|
|
209
|
+
} else if (lenByte === 0x81) {
|
|
210
|
+
nLen = bytes[idx + 2];
|
|
211
|
+
nStart = idx + 3;
|
|
212
|
+
} else {
|
|
213
|
+
nLen = lenByte;
|
|
214
|
+
nStart = idx + 2;
|
|
215
|
+
}
|
|
216
|
+
let nBytes = bytes.slice(nStart, nStart + nLen);
|
|
217
|
+
if (nBytes[0] === 0x00) nBytes = nBytes.slice(1);
|
|
218
|
+
|
|
219
|
+
idx = nStart + nLen;
|
|
220
|
+
while (idx < bytes.length && bytes[idx] !== 0x02) idx++;
|
|
221
|
+
const eLen = bytes[idx + 1];
|
|
222
|
+
const eBytes = bytes.slice(idx + 2, idx + 2 + eLen);
|
|
223
|
+
|
|
224
|
+
let nHex = '';
|
|
225
|
+
for (let b of nBytes) nHex += b.toString(16).padStart(2, '0');
|
|
226
|
+
let eHex = '';
|
|
227
|
+
for (let b of eBytes) eHex += b.toString(16).padStart(2, '0');
|
|
228
|
+
|
|
229
|
+
return { n: BigInt('0x' + nHex), e: BigInt('0x' + eHex), keyLen: nBytes.length };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function modPow(b, exp, mod) {
|
|
233
|
+
let res = 1n;
|
|
234
|
+
let base = b % mod;
|
|
235
|
+
let e = exp;
|
|
236
|
+
while (e > 0n) {
|
|
237
|
+
if (e & 1n) res = (res * base) % mod;
|
|
238
|
+
base = (base * base) % mod;
|
|
239
|
+
e >>= 1n;
|
|
240
|
+
}
|
|
241
|
+
return res;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function rsaEncryptPureJs(plainText, pubPem) {
|
|
245
|
+
const { n, e, keyLen } = parseSpkiKey(pubPem);
|
|
246
|
+
const msgBytes = new TextEncoder().encode(plainText);
|
|
247
|
+
const padLen = keyLen - 3 - msgBytes.length;
|
|
248
|
+
const pad = [];
|
|
249
|
+
while (pad.length < padLen) {
|
|
250
|
+
const r = Math.floor(Math.random() * 255) + 1;
|
|
251
|
+
pad.push(r);
|
|
252
|
+
}
|
|
253
|
+
const em = new Uint8Array(keyLen);
|
|
254
|
+
em[0] = 0x00;
|
|
255
|
+
em[1] = 0x02;
|
|
256
|
+
em.set(pad, 2);
|
|
257
|
+
em[2 + padLen] = 0x00;
|
|
258
|
+
em.set(msgBytes, 3 + padLen);
|
|
259
|
+
|
|
260
|
+
let emHex = '';
|
|
261
|
+
for (let b of em) emHex += b.toString(16).padStart(2, '0');
|
|
262
|
+
const m = BigInt('0x' + emHex);
|
|
263
|
+
const c = modPow(m, e, n);
|
|
264
|
+
let cHex = c.toString(16);
|
|
265
|
+
if (cHex.length % 2 !== 0) cHex = '0' + cHex;
|
|
266
|
+
while (cHex.length < keyLen * 2) cHex = '00' + cHex;
|
|
267
|
+
|
|
268
|
+
let binary = '';
|
|
269
|
+
for (let i = 0; i < cHex.length; i += 2) {
|
|
270
|
+
binary += String.fromCharCode(parseInt(cHex.slice(i, i + 2), 16));
|
|
271
|
+
}
|
|
272
|
+
return window.btoa(binary);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function encryptCredential(plainText) {
|
|
276
|
+
try {
|
|
277
|
+
const encryptedB64 = rsaEncryptPureJs(plainText, SERVER_RSA_KEY);
|
|
278
|
+
return { encryptedCredential: encryptedB64 };
|
|
279
|
+
} catch (err) {
|
|
280
|
+
console.error('RSA encrypt error:', err);
|
|
281
|
+
return { credential: plainText };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 自动检测 URL 参数里的 token 快速登录
|
|
286
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
287
|
+
const queryToken = urlParams.get('token');
|
|
288
|
+
if (queryToken) {
|
|
289
|
+
input.value = queryToken;
|
|
290
|
+
doVerify(queryToken);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
form.addEventListener('submit', (e) => {
|
|
294
|
+
e.preventDefault();
|
|
295
|
+
const val = input.value.trim();
|
|
296
|
+
if (!val) return;
|
|
297
|
+
doVerify(val);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
async function doVerify(credential) {
|
|
301
|
+
submitBtn.disabled = true;
|
|
302
|
+
submitBtn.innerText = ${JSON.stringify(i18n.verifying)};
|
|
303
|
+
statusMsg.className = 'status-msg';
|
|
304
|
+
statusMsg.style.display = 'none';
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const payload = encryptCredential(credential);
|
|
308
|
+
const res = await fetch('/api/remote-mobile/verify', {
|
|
309
|
+
method: 'POST',
|
|
310
|
+
headers: { 'Content-Type': 'application/json' },
|
|
311
|
+
body: JSON.stringify(payload)
|
|
312
|
+
});
|
|
313
|
+
const data = await res.json();
|
|
314
|
+
if (data.success) {
|
|
315
|
+
statusMsg.className = 'status-msg success';
|
|
316
|
+
statusMsg.innerText = ${JSON.stringify(i18n.success)};
|
|
317
|
+
statusMsg.style.display = 'block';
|
|
318
|
+
setTimeout(() => {
|
|
319
|
+
window.location.href = '/';
|
|
320
|
+
}, 600);
|
|
321
|
+
} else {
|
|
322
|
+
statusMsg.className = 'status-msg error';
|
|
323
|
+
statusMsg.innerText = data.reason || (isEn ? 'Authorization failed, please try again' : '授权失败,请重试');
|
|
324
|
+
statusMsg.style.display = 'block';
|
|
325
|
+
submitBtn.disabled = false;
|
|
326
|
+
submitBtn.innerText = ${JSON.stringify(i18n.submitBtn)};
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
statusMsg.className = 'status-msg error';
|
|
330
|
+
statusMsg.innerText = ${JSON.stringify(i18n.networkErr)};
|
|
331
|
+
statusMsg.style.display = 'block';
|
|
332
|
+
submitBtn.disabled = false;
|
|
333
|
+
submitBtn.innerText = ${JSON.stringify(i18n.submitBtn)};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
</script>
|
|
337
|
+
</body>
|
|
338
|
+
</html>`;
|
|
339
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 移动端与远程访问安全认证 - 核心类型定义
|
|
3
|
+
*/
|
|
4
|
+
export interface PluginConfig {
|
|
5
|
+
allowTailscale?: boolean;
|
|
6
|
+
allowLan?: boolean;
|
|
7
|
+
secret?: string;
|
|
8
|
+
secretHash?: string;
|
|
9
|
+
maxVisitsPerMinute?: number;
|
|
10
|
+
maxFailedAttempts?: number;
|
|
11
|
+
lockDurationMs?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface SessionInfo {
|
|
14
|
+
token: string;
|
|
15
|
+
ip: string;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
lastSeenAt: number;
|
|
18
|
+
userAgent?: string;
|
|
19
|
+
deviceName?: string;
|
|
20
|
+
authType: string;
|
|
21
|
+
isBypass?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface ShortCodeInfo {
|
|
24
|
+
code: string;
|
|
25
|
+
token: string;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
}
|
|
29
|
+
export interface IpSecurityStat {
|
|
30
|
+
ip: string;
|
|
31
|
+
authVisits: number;
|
|
32
|
+
firstSeenAt: number;
|
|
33
|
+
lastSeenAt: number;
|
|
34
|
+
failedAttempts: number;
|
|
35
|
+
totalAttempts: number;
|
|
36
|
+
lockedUntil?: number;
|
|
37
|
+
lockCount: number;
|
|
38
|
+
authType?: string;
|
|
39
|
+
deviceName?: string;
|
|
40
|
+
userAgent?: string;
|
|
41
|
+
}
|
|
42
|
+
export type DeviceEventType = 'device-connected' | 'device-online' | 'device-revoked' | 'ip-security-alert' | 'ip-security-updated';
|
|
43
|
+
export interface DeviceEventPayload {
|
|
44
|
+
type: DeviceEventType;
|
|
45
|
+
device?: SessionInfo;
|
|
46
|
+
ip?: string;
|
|
47
|
+
reason?: string;
|
|
48
|
+
stat?: IpSecurityStat;
|
|
49
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-remote-mobile",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "DeepSeek Hub (DSH) Mobile & Remote Access Security Guard Plugin: Tailscale & LAN bypass, QR scan auth, RSA encryption, brute-force protection, and realtime SSE push.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"deepseek",
|
|
9
|
+
"tailscale",
|
|
10
|
+
"remote-access",
|
|
11
|
+
"mobile",
|
|
12
|
+
"cordis-plugin",
|
|
13
|
+
"auth"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/IceApriler/dsh-remote-mobile#readme",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/IceApriler/dsh-remote-mobile.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/IceApriler/dsh-remote-mobile/issues"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "lib/index.js",
|
|
25
|
+
"types": "lib/index.d.ts",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc && node scripts/build-client.js",
|
|
28
|
+
"build:server": "tsc",
|
|
29
|
+
"build:client": "node scripts/build-client.js",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"pretest": "node scripts/build-client.js",
|
|
32
|
+
"test": "node --test test/**/*.test.js",
|
|
33
|
+
"prepublishOnly": "npm run build && npm test",
|
|
34
|
+
"release": "node scripts/release.js",
|
|
35
|
+
"release:patch": "node scripts/release.js patch",
|
|
36
|
+
"release:minor": "node scripts/release.js minor",
|
|
37
|
+
"release:major": "node scripts/release.js major",
|
|
38
|
+
"release:current": "node scripts/release.js current"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"registry": "https://registry.npmjs.org/"
|
|
43
|
+
},
|
|
44
|
+
"exports": {
|
|
45
|
+
".": "./lib/index.js",
|
|
46
|
+
"./client": "./lib/client.js",
|
|
47
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
48
|
+
"./package.json": "./package.json"
|
|
49
|
+
},
|
|
50
|
+
"dsh": {
|
|
51
|
+
"bundle": {
|
|
52
|
+
"patch": "./cordis.patch.yml"
|
|
53
|
+
},
|
|
54
|
+
"client": {
|
|
55
|
+
"inject": [
|
|
56
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
57
|
+
"@deepseek-ai/dsh-client-connection",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
60
|
+
],
|
|
61
|
+
"platform": "web"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"files": [
|
|
65
|
+
"lib",
|
|
66
|
+
"cordis.patch.yml",
|
|
67
|
+
"README.md",
|
|
68
|
+
"README_EN.md",
|
|
69
|
+
"LICENSE"
|
|
70
|
+
],
|
|
71
|
+
"license": "MIT",
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "^26.2.0",
|
|
74
|
+
"esbuild": "^0.28.2",
|
|
75
|
+
"typescript": "^7.0.2"
|
|
76
|
+
}
|
|
77
|
+
}
|