@having5548/dsh-downloader 0.0.0-stage → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ package main
2
+
3
+ import (
4
+ "crypto/rand"
5
+ "net"
6
+
7
+ "golang.org/x/crypto/blake2b"
8
+ )
9
+
10
+ // salamanderSaltLen is the random salt prefixed to every datagram.
11
+ const salamanderSaltLen = 8
12
+
13
+ // salamanderPacketConn applies hysteria2's "salamander" obfuscation to every
14
+ // datagram: an 8-byte random salt, followed by the payload XORed with a
15
+ // BLAKE2b-256 keystream keyed by password||salt. Without it the server simply
16
+ // drops our packets — the QUIC handshake never completes.
17
+ //
18
+ // Mirrors github.com/sagernet/sing-quic/hysteria2 (GPL-3.0) and the hysteria2
19
+ // reference; this is the wire format, not an implementation detail.
20
+ type salamanderPacketConn struct {
21
+ net.PacketConn
22
+ password []byte
23
+ }
24
+
25
+ // newSalamanderPacketConn wraps conn, or returns it untouched when no password
26
+ // is configured (plain hysteria2 servers exist).
27
+ func newSalamanderPacketConn(conn net.PacketConn, password string) net.PacketConn {
28
+ if password == "" {
29
+ return conn
30
+ }
31
+ return &salamanderPacketConn{PacketConn: conn, password: []byte(password)}
32
+ }
33
+
34
+ // keystream returns the per-packet key for a salt, without aliasing password.
35
+ func (s *salamanderPacketConn) keystream(salt []byte) [blake2b.Size256]byte {
36
+ seed := make([]byte, 0, len(s.password)+len(salt))
37
+ seed = append(seed, s.password...)
38
+ seed = append(seed, salt...)
39
+ return blake2b.Sum256(seed)
40
+ }
41
+
42
+ // SetReadBuffer / SetWriteBuffer keep quic-go's socket tuning working: without
43
+ // them the transport logs "connection doesn't allow setting of receive buffer
44
+ // size" and falls back to smaller buffers, which costs throughput.
45
+ func (s *salamanderPacketConn) SetReadBuffer(bytes int) error {
46
+ if setter, ok := s.PacketConn.(interface{ SetReadBuffer(int) error }); ok {
47
+ return setter.SetReadBuffer(bytes)
48
+ }
49
+ return nil
50
+ }
51
+
52
+ func (s *salamanderPacketConn) SetWriteBuffer(bytes int) error {
53
+ if setter, ok := s.PacketConn.(interface{ SetWriteBuffer(int) error }); ok {
54
+ return setter.SetWriteBuffer(bytes)
55
+ }
56
+ return nil
57
+ }
58
+
59
+ func (s *salamanderPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
60
+ n, addr, err := s.PacketConn.ReadFrom(p)
61
+ if err != nil || n <= salamanderSaltLen {
62
+ return n, addr, err
63
+ }
64
+ key := s.keystream(p[:salamanderSaltLen])
65
+ for index, c := range p[salamanderSaltLen:n] {
66
+ p[index] = c ^ key[index%blake2b.Size256]
67
+ }
68
+ return n - salamanderSaltLen, addr, nil
69
+ }
70
+
71
+ func (s *salamanderPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {
72
+ buffer := make([]byte, salamanderSaltLen+len(p))
73
+ if _, err := rand.Read(buffer[:salamanderSaltLen]); err != nil {
74
+ return 0, err
75
+ }
76
+ key := s.keystream(buffer[:salamanderSaltLen])
77
+ for index, c := range p {
78
+ buffer[salamanderSaltLen+index] = c ^ key[index%blake2b.Size256]
79
+ }
80
+ if _, err := s.PacketConn.WriteTo(buffer, addr); err != nil {
81
+ return 0, err
82
+ }
83
+ return len(p), nil
84
+ }
@@ -0,0 +1,455 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "crypto/rand"
6
+ "encoding/hex"
7
+ "fmt"
8
+ "io"
9
+ "math/big"
10
+ "net"
11
+ "reflect"
12
+ "time"
13
+ "unsafe"
14
+
15
+ utls "github.com/metacubex/utls"
16
+ )
17
+
18
+ // ---- vless request ---------------------------------------------------------
19
+
20
+ func buildVlessRequest(node *NodeConfig, host string, port uint16) []byte {
21
+ uuid, err := hex.DecodeString(stringsReplace(node.UUID, "-", ""))
22
+ if err != nil {
23
+ panic(fmt.Sprintf("bad uuid: %v", err))
24
+ }
25
+ var addons []byte
26
+ if node.Flow == "xtls-rprx-vision" {
27
+ flowBytes := []byte("xtls-rprx-vision")
28
+ addons = append(addons, 0x0a)
29
+ addons = appendUvarint(addons, uint64(len(flowBytes)))
30
+ addons = append(addons, flowBytes...)
31
+ }
32
+ var buf []byte
33
+ buf = append(buf, 0x00)
34
+ buf = append(buf, uuid...)
35
+ buf = append(buf, byte(len(addons)))
36
+ buf = append(buf, addons...)
37
+ buf = append(buf, 0x01) // cmd TCP
38
+ buf = append(buf, byte(port>>8), byte(port&0xff))
39
+ buf = append(buf, encodeVlessAddr(host)...)
40
+ return buf
41
+ }
42
+
43
+ func encodeVlessAddr(host string) []byte {
44
+ if ip := net.ParseIP(host); ip != nil {
45
+ if ip4 := ip.To4(); ip4 != nil {
46
+ return append([]byte{0x01}, ip4...)
47
+ }
48
+ return append([]byte{0x03}, ip.To16()...)
49
+ }
50
+ b := []byte(host)
51
+ return append([]byte{0x02, byte(len(b))}, b...)
52
+ }
53
+
54
+ func appendUvarint(b []byte, v uint64) []byte {
55
+ for v >= 0x80 {
56
+ b = append(b, byte(v)|0x80)
57
+ v >>= 7
58
+ }
59
+ return append(b, byte(v))
60
+ }
61
+
62
+ func stringsReplace(s, old, new string) string {
63
+ out := ""
64
+ for i := 0; i < len(s); i++ {
65
+ if i+len(old) <= len(s) && s[i:i+len(old)] == old {
66
+ out += new
67
+ i += len(old) - 1
68
+ } else {
69
+ out += s[i : i+1]
70
+ }
71
+ }
72
+ return out
73
+ }
74
+
75
+ // ---- vless logical conn (request on first write, response on first read) ---
76
+
77
+ type vlessConn struct {
78
+ conn net.Conn
79
+ request []byte
80
+ requestWritten bool
81
+ responseRead bool
82
+ }
83
+
84
+ func newVlessConn(conn net.Conn, node *NodeConfig, dest string) (*vlessConn, error) {
85
+ host, portStr, err := net.SplitHostPort(dest)
86
+ if err != nil {
87
+ return nil, err
88
+ }
89
+ var portNum int
90
+ fmt.Sscanf(portStr, "%d", &portNum)
91
+ return &vlessConn{conn: conn, request: buildVlessRequest(node, host, uint16(portNum))}, nil
92
+ }
93
+
94
+ func (c *vlessConn) Write(b []byte) (int, error) {
95
+ if !c.requestWritten {
96
+ payload := append(append([]byte{}, c.request...), b...)
97
+ if _, err := c.conn.Write(payload); err != nil {
98
+ return 0, err
99
+ }
100
+ c.requestWritten = true
101
+ return len(b), nil
102
+ }
103
+ return c.conn.Write(b)
104
+ }
105
+
106
+ func (c *vlessConn) Read(b []byte) (int, error) {
107
+ if !c.responseRead {
108
+ var head [2]byte
109
+ if _, err := io.ReadFull(c.conn, head[:]); err != nil {
110
+ return 0, err
111
+ }
112
+ if head[0] != 0x00 {
113
+ return 0, fmt.Errorf("vless bad response version %d", head[0])
114
+ }
115
+ if head[1] > 0 {
116
+ if _, err := io.ReadFull(c.conn, make([]byte, head[1])); err != nil {
117
+ return 0, err
118
+ }
119
+ }
120
+ c.responseRead = true
121
+ }
122
+ return c.conn.Read(b)
123
+ }
124
+
125
+ func (c *vlessConn) Close() error { return c.conn.Close() }
126
+ func (c *vlessConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
127
+ func (c *vlessConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
128
+ func (c *vlessConn) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) }
129
+ func (c *vlessConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) }
130
+ func (c *vlessConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) }
131
+
132
+ // ---- xtls-rprx-vision client (padding + XTLS direct) ----------------------
133
+
134
+ const xrayChunkSize = 8192
135
+
136
+ var (
137
+ tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}
138
+ tlsClientHandShakeStart = []byte{0x16, 0x03}
139
+ tlsServerHandShakeStart = []byte{0x16, 0x03, 0x03}
140
+ tlsApplicationDataStart = []byte{0x17, 0x03, 0x03}
141
+ )
142
+
143
+ const (
144
+ commandPaddingContinue byte = iota
145
+ commandPaddingEnd
146
+ commandPaddingDirect
147
+ )
148
+
149
+ var tls13CipherSuiteDic = map[uint16]string{
150
+ 0x1301: "TLS_AES_128_GCM_SHA256",
151
+ 0x1302: "TLS_AES_256_GCM_SHA384",
152
+ 0x1303: "TLS_CHACHA20_POLY1305_SHA256",
153
+ 0x1304: "TLS_AES_128_CCM_SHA256",
154
+ 0x1305: "TLS_AES_128_CCM_8_SHA256",
155
+ }
156
+
157
+ type visionConn struct {
158
+ net.Conn // the vless logical conn (reads/writes raw TLS stream)
159
+
160
+ rawConn net.Conn // underlying TCP conn of the uTLS conn (for XTLS direct)
161
+ input *bytes.Reader
162
+ rawIn *bytes.Buffer
163
+
164
+ userUUID [16]byte
165
+ isTLS bool
166
+ numberOfPacketToFilter int
167
+ isTLS12orAbove bool
168
+ remainingServerHello int32
169
+ cipher uint16
170
+ enableXTLS bool
171
+ isPadding bool
172
+ directWrite bool
173
+ writeUUID bool
174
+ withinPaddingBuffers bool
175
+ remainingContent int
176
+ remainingPadding int
177
+ currentCommand byte
178
+ directRead bool
179
+ remainingReader io.Reader
180
+ }
181
+
182
+ func newVisionConn(vconn *vlessConn, uconn *utls.UConn, uuid [16]byte) (*visionConn, error) {
183
+ // Extract the uTLS conn's internal input/rawInput for XTLS splice.
184
+ connType := reflect.TypeOf(uconn.Conn).Elem()
185
+ inputField, ok1 := connType.FieldByName("input")
186
+ rawInputField, ok2 := connType.FieldByName("rawInput")
187
+ if !ok1 || !ok2 {
188
+ return nil, fmt.Errorf("vision: utls.Conn has no input/rawInput fields")
189
+ }
190
+ connPtr := uintptr(unsafe.Pointer(uconn.Conn))
191
+ return &visionConn{
192
+ Conn: vconn,
193
+ rawConn: uconn.NetConn(),
194
+ input: (*bytes.Reader)(unsafe.Pointer(connPtr + inputField.Offset)),
195
+ rawIn: (*bytes.Buffer)(unsafe.Pointer(connPtr + rawInputField.Offset)),
196
+ userUUID: uuid,
197
+ numberOfPacketToFilter: 8,
198
+ remainingServerHello: -1,
199
+ isPadding: true,
200
+ writeUUID: true,
201
+ withinPaddingBuffers: true,
202
+ remainingContent: -1,
203
+ remainingPadding: -1,
204
+ }, nil
205
+ }
206
+
207
+ func (c *visionConn) Read(p []byte) (n int, err error) {
208
+ if c.remainingReader != nil {
209
+ n, err = c.remainingReader.Read(p)
210
+ if err == io.EOF {
211
+ err = nil
212
+ c.remainingReader = nil
213
+ }
214
+ if n > 0 {
215
+ return
216
+ }
217
+ }
218
+ if c.directRead {
219
+ return c.rawConn.Read(p)
220
+ }
221
+ var buffer []byte
222
+ if len(p) > xrayChunkSize {
223
+ n, err = c.Conn.Read(p)
224
+ if err != nil {
225
+ return
226
+ }
227
+ buffer = p[:n]
228
+ } else {
229
+ chunk := make([]byte, xrayChunkSize)
230
+ n, err = c.Conn.Read(chunk)
231
+ if err != nil && n == 0 {
232
+ return 0, err
233
+ }
234
+ buffer = chunk[:n]
235
+ }
236
+ if c.withinPaddingBuffers || c.numberOfPacketToFilter > 0 {
237
+ buffers := c.unPadding(buffer)
238
+ if c.remainingContent == 0 && c.remainingPadding == 0 {
239
+ if c.currentCommand == commandPaddingEnd {
240
+ c.withinPaddingBuffers = false
241
+ c.remainingContent = -1
242
+ c.remainingPadding = -1
243
+ } else if c.currentCommand == commandPaddingDirect {
244
+ c.withinPaddingBuffers = false
245
+ c.directRead = true
246
+ if ib, e := io.ReadAll(c.input); e == nil && len(ib) > 0 {
247
+ buffers = append(buffers, ib)
248
+ }
249
+ if rb, e := io.ReadAll(c.rawIn); e == nil && len(rb) > 0 {
250
+ buffers = append(buffers, rb)
251
+ }
252
+ } else if c.currentCommand == commandPaddingContinue {
253
+ c.withinPaddingBuffers = true
254
+ } else {
255
+ return 0, fmt.Errorf("vision: unknown command %d", c.currentCommand)
256
+ }
257
+ } else if c.remainingContent > 0 || c.remainingPadding > 0 {
258
+ c.withinPaddingBuffers = true
259
+ } else {
260
+ c.withinPaddingBuffers = false
261
+ }
262
+ if c.numberOfPacketToFilter > 0 {
263
+ for _, buf := range buffers {
264
+ c.filterTLS(buf)
265
+ }
266
+ }
267
+ readers := make([]io.Reader, len(buffers))
268
+ for i, b := range buffers {
269
+ readers[i] = bytes.NewReader(b)
270
+ }
271
+ c.remainingReader = io.MultiReader(readers...)
272
+ return c.Read(p)
273
+ }
274
+ if c.numberOfPacketToFilter > 0 {
275
+ c.filterTLS(buffer)
276
+ }
277
+ return n, err
278
+ }
279
+
280
+ func (c *visionConn) Write(p []byte) (n int, err error) {
281
+ if c.numberOfPacketToFilter > 0 {
282
+ c.filterTLS(p)
283
+ }
284
+ if c.isPadding {
285
+ inputLen := len(p)
286
+ buffers := reshapeBuffer(p)
287
+ var specIndex int
288
+ for i, buffer := range buffers {
289
+ if c.isTLS && len(buffer) > 6 && bytes.Equal(tlsApplicationDataStart, buffer[:3]) {
290
+ command := byte(commandPaddingEnd)
291
+ if c.enableXTLS {
292
+ c.directWrite = true
293
+ specIndex = i
294
+ command = commandPaddingDirect
295
+ }
296
+ c.isPadding = false
297
+ buffers[i] = c.padding(buffer, command)
298
+ break
299
+ } else if !c.isTLS12orAbove && c.numberOfPacketToFilter <= 1 {
300
+ c.isPadding = false
301
+ buffers[i] = c.padding(buffer, commandPaddingEnd)
302
+ break
303
+ }
304
+ buffers[i] = c.padding(buffer, commandPaddingContinue)
305
+ }
306
+ if c.directWrite {
307
+ // Write the padded prefix through the vision layer, then switch the
308
+ // rest of the stream to raw TLS-bypass (XTLS splice).
309
+ for i := 0; i <= specIndex; i++ {
310
+ if _, err = c.Conn.Write(buffers[i]); err != nil {
311
+ return
312
+ }
313
+ }
314
+ buffers = buffers[specIndex+1:]
315
+ c.Conn = c.rawConn
316
+ time.Sleep(5 * time.Millisecond)
317
+ }
318
+ for _, buffer := range buffers {
319
+ if _, err = c.Conn.Write(buffer); err != nil {
320
+ return
321
+ }
322
+ }
323
+ n = inputLen
324
+ return
325
+ }
326
+ if c.directWrite {
327
+ return c.rawConn.Write(p)
328
+ }
329
+ return c.Conn.Write(p)
330
+ }
331
+
332
+ func (c *visionConn) filterTLS(buffer []byte) {
333
+ c.numberOfPacketToFilter--
334
+ if len(buffer) > 6 {
335
+ if buffer[0] == 22 && buffer[1] == 3 && buffer[2] == 3 {
336
+ c.isTLS = true
337
+ if buffer[5] == 2 {
338
+ c.isTLS12orAbove = true
339
+ c.remainingServerHello = (int32(buffer[3])<<8 | int32(buffer[4])) + 5
340
+ if len(buffer) >= 79 && c.remainingServerHello >= 79 {
341
+ sessionIDLen := int32(buffer[43])
342
+ cipherSuite := buffer[43+sessionIDLen+1 : 43+sessionIDLen+3]
343
+ c.cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1])
344
+ }
345
+ }
346
+ } else if bytes.Equal(tlsClientHandShakeStart, buffer[:2]) && buffer[5] == 1 {
347
+ c.isTLS = true
348
+ }
349
+ }
350
+ if c.remainingServerHello > 0 {
351
+ end := int(c.remainingServerHello)
352
+ if end > len(buffer) {
353
+ end = len(buffer)
354
+ }
355
+ c.remainingServerHello -= int32(end)
356
+ if bytes.Contains(buffer[:end], tls13SupportedVersions) {
357
+ cipher, ok := tls13CipherSuiteDic[c.cipher]
358
+ if ok && cipher != "TLS_AES_128_CCM_8_SHA256" {
359
+ c.enableXTLS = true
360
+ }
361
+ c.numberOfPacketToFilter = 0
362
+ } else if c.remainingServerHello == 0 {
363
+ c.numberOfPacketToFilter = 0
364
+ }
365
+ }
366
+ }
367
+
368
+ func (c *visionConn) padding(buffer []byte, command byte) []byte {
369
+ contentLen := len(buffer)
370
+ paddingLen := 0
371
+ if contentLen < 900 && c.isTLS {
372
+ l, _ := rand.Int(rand.Reader, big.NewInt(500))
373
+ paddingLen = int(l.Int64()) + 900 - contentLen
374
+ } else {
375
+ l, _ := rand.Int(rand.Reader, big.NewInt(256))
376
+ paddingLen = int(l.Int64())
377
+ }
378
+ bufferLen := paddingLen + contentLen + 5
379
+ if c.writeUUID {
380
+ bufferLen += 16
381
+ }
382
+ out := make([]byte, 0, bufferLen)
383
+ if c.writeUUID {
384
+ out = append(out, c.userUUID[:]...)
385
+ c.writeUUID = false
386
+ }
387
+ out = append(out, command, byte(contentLen>>8), byte(contentLen), byte(paddingLen>>8), byte(paddingLen))
388
+ out = append(out, buffer...)
389
+ out = append(out, make([]byte, paddingLen)...)
390
+ return out
391
+ }
392
+
393
+ func (c *visionConn) unPadding(buffer []byte) [][]byte {
394
+ var bufferIndex int
395
+ if c.remainingContent == -1 && c.remainingPadding == -1 {
396
+ if len(buffer) >= 21 && bytes.Equal(c.userUUID[:], buffer[:16]) {
397
+ bufferIndex = 16
398
+ c.remainingContent = 0
399
+ c.remainingPadding = 0
400
+ c.currentCommand = 0
401
+ }
402
+ }
403
+ if c.remainingContent == -1 && c.remainingPadding == -1 {
404
+ return [][]byte{buffer}
405
+ }
406
+ var buffers [][]byte
407
+ for bufferIndex < len(buffer) {
408
+ if c.remainingContent <= 0 && c.remainingPadding <= 0 {
409
+ if c.currentCommand == commandPaddingEnd {
410
+ buffers = append(buffers, buffer[bufferIndex:])
411
+ break
412
+ } else {
413
+ if bufferIndex+5 > len(buffer) {
414
+ break
415
+ }
416
+ paddingInfo := buffer[bufferIndex : bufferIndex+5]
417
+ c.currentCommand = paddingInfo[0]
418
+ c.remainingContent = int(paddingInfo[1])<<8 | int(paddingInfo[2])
419
+ c.remainingPadding = int(paddingInfo[3])<<8 | int(paddingInfo[4])
420
+ bufferIndex += 5
421
+ }
422
+ } else if c.remainingContent > 0 {
423
+ end := c.remainingContent
424
+ if end > len(buffer)-bufferIndex {
425
+ end = len(buffer) - bufferIndex
426
+ }
427
+ buffers = append(buffers, buffer[bufferIndex:bufferIndex+end])
428
+ c.remainingContent -= end
429
+ bufferIndex += end
430
+ } else {
431
+ end := c.remainingPadding
432
+ if end > len(buffer)-bufferIndex {
433
+ end = len(buffer) - bufferIndex
434
+ }
435
+ c.remainingPadding -= end
436
+ bufferIndex += end
437
+ }
438
+ if bufferIndex == len(buffer) {
439
+ break
440
+ }
441
+ }
442
+ return buffers
443
+ }
444
+
445
+ func reshapeBuffer(b []byte) [][]byte {
446
+ const bufferLimit = 8192 - 21
447
+ if len(b) < bufferLimit {
448
+ return [][]byte{b}
449
+ }
450
+ index := int(bytes.LastIndex(b, tlsApplicationDataStart))
451
+ if index <= 0 {
452
+ index = 8192 / 2
453
+ }
454
+ return [][]byte{b[:index], b[index:]}
455
+ }
package/package.json CHANGED
@@ -1,6 +1,88 @@
1
1
  {
2
2
  "name": "@having5548/dsh-downloader",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.5.2",
4
+ "description": "DeepSeek Harness 下载代理插件:注册 dsh_download 等工具,让模型下载境外文件时自动走插件自带的代理内核(订阅自包含,或显式 proxyUrl),境内直连;带 sha256、大小上限、停滞检测与直连失败自动回退,另附「设置 → 下载代理」管理面板。",
5
+ "keywords": [
6
+ "dsh",
7
+ "dsh-plugin",
8
+ "deepseek-harness",
9
+ "download",
10
+ "proxy",
11
+ "clash",
12
+ "subscription",
13
+ "smart-routing"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/having5548/dsh-downloader.git"
18
+ },
19
+ "homepage": "https://github.com/having5548/dsh-downloader#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/having5548/dsh-downloader/issues"
22
+ },
23
+ "type": "module",
24
+ "main": "lib/index.js",
25
+ "exports": {
26
+ ".": {
27
+ "default": "./lib/index.js"
28
+ },
29
+ "./client": {
30
+ "default": "./lib/client.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "lib",
36
+ "native",
37
+ "cordis.patch.yml",
38
+ "README.md",
39
+ "README.en.md",
40
+ "CHANGELOG.md",
41
+ "LICENSE"
42
+ ],
43
+ "engines": {
44
+ "node": ">=20"
45
+ },
46
+ "scripts": {
47
+ "test": "node test/smoke.mjs",
48
+ "test:e2e": "node test/e2e.mjs"
49
+ },
50
+ "dsh": {
51
+ "bundle": {
52
+ "patch": "./cordis.patch.yml"
53
+ },
54
+ "client": {
55
+ "platform": "web",
56
+ "inject": [
57
+ "@deepseek-ai/dsh-client-runtime",
58
+ "@deepseek-ai/dsh-client-locale",
59
+ "@deepseek-ai/dsh-client-ui-settings"
60
+ ]
61
+ }
62
+ },
63
+ "dependencies": {
64
+ "@deepseek-ai/cosmokit": "^1.8.5",
65
+ "@deepseek-ai/schemastery": "^3.18.4",
66
+ "js-yaml": "^4.1.0"
67
+ },
68
+ "peerDependencies": {
69
+ "@deepseek-ai/dsh-client-runtime": "*",
70
+ "react": "^18.2.0",
71
+ "react-dom": "^18.2.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "@deepseek-ai/dsh-client-runtime": {
75
+ "optional": true
76
+ },
77
+ "react": {
78
+ "optional": true
79
+ },
80
+ "react-dom": {
81
+ "optional": true
82
+ }
83
+ },
84
+ "license": "MIT",
85
+ "publishConfig": {
86
+ "access": "public"
87
+ }
88
+ }