wallpaper-engine-dsh 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/README.md +107 -0
- package/README.zh.md +109 -0
- package/cordis.patch.yml +18 -0
- package/lib/client.js +1052 -0
- package/lib/core.js +499 -0
- package/lib/index.js +315 -0
- package/lib/types/client.d.ts +18 -0
- package/lib/types/index.d.ts +50 -0
- package/package.json +81 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wallpaper-engine-dsh — host half.
|
|
3
|
+
*
|
|
4
|
+
* A Cordis plugin (loaded as an out-of-tree bundle row, see cordis.patch.yml)
|
|
5
|
+
* that bridges the local Wallpaper Engine install into the DSH web GUI.
|
|
6
|
+
* All parsing/enumeration logic lives in the pure layer (lib/core.js); this
|
|
7
|
+
* file only wires it into same-origin HTTP routes on `ctx.webServer`:
|
|
8
|
+
*
|
|
9
|
+
* GET /we-background/inventory[?refresh=1] → { installDir, wallpapers, playlists }
|
|
10
|
+
* GET /we-background/media/<token>[/<sub-asset>…] → video / web HTML + assets
|
|
11
|
+
* GET /we-background/preview/<token> → preview image
|
|
12
|
+
*
|
|
13
|
+
* Security model
|
|
14
|
+
* - Tokens are 72-bit random values minted per inventory build (not
|
|
15
|
+
* reversible encodings of paths). Knowing a filesystem path grants
|
|
16
|
+
* nothing; only files enumerated into the inventory are reachable.
|
|
17
|
+
* Rebuilding the inventory re-mints every token, so stale URLs die.
|
|
18
|
+
* - Web-wallpaper sub-assets resolve strictly INSIDE their project
|
|
19
|
+
* directory (core.isInsideDir) — `..` traversal is answered 403.
|
|
20
|
+
* - project.json-declared files must also be inside the project directory
|
|
21
|
+
* (core.readProject), so a hostile workshop item cannot point the server
|
|
22
|
+
* at arbitrary files.
|
|
23
|
+
* - The browser half renders web wallpapers in a sandboxed iframe
|
|
24
|
+
* (`sandbox="allow-scripts"`, no allow-same-origin), so third-party
|
|
25
|
+
* wallpaper JS gets an opaque origin and cannot touch DSH storage/APIs.
|
|
26
|
+
* - Everything is loopback same-origin; `X-Content-Type-Options: nosniff`
|
|
27
|
+
* on every media response.
|
|
28
|
+
*
|
|
29
|
+
* Lifecycle: every route registers through the plugin fiber and unwinds on
|
|
30
|
+
* unload; the inventory cache and token map are dropped with it. The plugin
|
|
31
|
+
* contributes no model-visible tool and no prompt text.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import {
|
|
35
|
+
createReadStream,
|
|
36
|
+
existsSync,
|
|
37
|
+
readFileSync,
|
|
38
|
+
statSync,
|
|
39
|
+
} from 'node:fs';
|
|
40
|
+
import { execFileSync } from 'node:child_process';
|
|
41
|
+
import { randomBytes } from 'node:crypto';
|
|
42
|
+
import { join } from 'node:path';
|
|
43
|
+
|
|
44
|
+
import * as core from './core.js';
|
|
45
|
+
|
|
46
|
+
/** Request path prefix under which this bundle's HTTP surface lives. */
|
|
47
|
+
const BASE = '/we-background';
|
|
48
|
+
/** Inventory is cached and rebuilt at most this often unless ?refresh=1. */
|
|
49
|
+
const INVENTORY_TTL_MS = 30_000;
|
|
50
|
+
/** Media kinds eligible for sub-asset serving (web wallpapers bundle files). */
|
|
51
|
+
const SUBASSET_KINDS = new Set(['web']);
|
|
52
|
+
|
|
53
|
+
/** Steam root recorded by the Windows installer; null elsewhere/on failure. */
|
|
54
|
+
function steamRegistryRoot() {
|
|
55
|
+
if (process.platform !== 'win32') return null;
|
|
56
|
+
try {
|
|
57
|
+
const reg = join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'reg.exe');
|
|
58
|
+
const out = execFileSync(
|
|
59
|
+
reg,
|
|
60
|
+
['query', 'HKCU\\Software\\Valve\\Steam', '/v', 'SteamPath'],
|
|
61
|
+
{ encoding: 'utf8', windowsHide: true, timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] },
|
|
62
|
+
);
|
|
63
|
+
return core.steamPathFromRegQuery(out);
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Full discovery pass: install dir + every library owning WE content. */
|
|
70
|
+
function discover() {
|
|
71
|
+
const probes = core.steamProbeDirs();
|
|
72
|
+
const registryRoot = steamRegistryRoot();
|
|
73
|
+
const searchRoots = registryRoot ? [registryRoot, ...probes] : probes;
|
|
74
|
+
const vdfLibraries = [];
|
|
75
|
+
for (const root of searchRoots) {
|
|
76
|
+
const vdf = join(root, 'steamapps', 'libraryfolders.vdf');
|
|
77
|
+
if (!existsSync(vdf)) continue;
|
|
78
|
+
try {
|
|
79
|
+
vdfLibraries.push(...core.librariesFromVdfText(readFileSync(vdf, 'utf8')));
|
|
80
|
+
} catch { /* unreadable vdf → skip this root */ }
|
|
81
|
+
}
|
|
82
|
+
const installDir = core.findInstallDir(
|
|
83
|
+
core.installDirCandidates({ registryRoot, libraryRoots: vdfLibraries }),
|
|
84
|
+
);
|
|
85
|
+
const libraryRoots = core.workshopLibraryRoots(searchRoots, vdfLibraries);
|
|
86
|
+
return { installDir, libraryRoots };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const inject = ['webServer'];
|
|
90
|
+
|
|
91
|
+
export function apply(ctx) {
|
|
92
|
+
const webServer = ctx.webServer;
|
|
93
|
+
if (!webServer || typeof webServer.register !== 'function') {
|
|
94
|
+
return () => {}; // defensive: inject guarantees the service in practice
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Token registry ──────────────────────────────────────────────────────
|
|
98
|
+
// token → { abs, rootDir|null }. rootDir is set for renderable projects so
|
|
99
|
+
// web wallpapers can fetch their bundled sub-assets (contained, see below).
|
|
100
|
+
const tokens = new Map();
|
|
101
|
+
const mint = (entry) => {
|
|
102
|
+
const token = randomBytes(9).toString('base64url'); // 72 bits, url-safe
|
|
103
|
+
tokens.set(token, entry);
|
|
104
|
+
return token;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// ── Inventory (cached) ──────────────────────────────────────────────────
|
|
108
|
+
let cache = null; // { at: number, payload: object }
|
|
109
|
+
|
|
110
|
+
function buildInventory() {
|
|
111
|
+
tokens.clear(); // re-mint: tokens from older builds stop resolving
|
|
112
|
+
const { installDir, libraryRoots } = discover();
|
|
113
|
+
const roots = core.wallpaperRoots(installDir, libraryRoots);
|
|
114
|
+
const projects = core.enumerateWallpapers(roots);
|
|
115
|
+
const byId = new Map(projects.map((p) => [p.id, p]));
|
|
116
|
+
const byPath = new Map(projects.map((p) => [core.pathKey(p.fileAbs), p.id]));
|
|
117
|
+
|
|
118
|
+
const wallpapers = projects.map((p) => {
|
|
119
|
+
const portable = core.PORTABLE_KINDS.includes(p.type);
|
|
120
|
+
const hasMedia = portable && existsSync(p.fileAbs);
|
|
121
|
+
const hasPreview = Boolean(p.previewAbs) && existsSync(p.previewAbs);
|
|
122
|
+
return {
|
|
123
|
+
id: p.id,
|
|
124
|
+
title: p.title,
|
|
125
|
+
type: p.type,
|
|
126
|
+
playable: hasMedia,
|
|
127
|
+
media: hasMedia
|
|
128
|
+
? `${BASE}/media/${mint({
|
|
129
|
+
abs: p.fileAbs,
|
|
130
|
+
rootDir: SUBASSET_KINDS.has(p.type) ? p.dir : null,
|
|
131
|
+
})}`
|
|
132
|
+
: null,
|
|
133
|
+
preview: hasPreview
|
|
134
|
+
? `${BASE}/preview/${mint({ abs: p.previewAbs, rootDir: null })}`
|
|
135
|
+
: null,
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
const playableIds = new Set(wallpapers.filter((w) => w.playable).map((w) => w.id));
|
|
139
|
+
|
|
140
|
+
let playlists = [];
|
|
141
|
+
const configPath = installDir ? join(installDir, 'config.json') : null;
|
|
142
|
+
if (configPath && existsSync(configPath)) {
|
|
143
|
+
try {
|
|
144
|
+
playlists = core.parsePlaylists(JSON.parse(readFileSync(configPath, 'utf8')));
|
|
145
|
+
} catch { /* malformed config.json → no playlists, not fatal */ }
|
|
146
|
+
}
|
|
147
|
+
const playlistView = playlists.map((playlist, index) => {
|
|
148
|
+
const ids = [];
|
|
149
|
+
const seen = new Set();
|
|
150
|
+
for (const item of playlist.items) {
|
|
151
|
+
const id = core.resolvePlaylistItem(item, byPath, byId);
|
|
152
|
+
if (id && !seen.has(id)) { seen.add(id); ids.push(id); }
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
id: `pl-${index}`,
|
|
156
|
+
name: playlist.name,
|
|
157
|
+
order: playlist.order,
|
|
158
|
+
delay: playlist.delay,
|
|
159
|
+
wallpaperIds: ids,
|
|
160
|
+
total: ids.length,
|
|
161
|
+
portableCount: ids.filter((id) => playableIds.has(id)).length,
|
|
162
|
+
unresolvedCount: Math.max(0, playlist.items.length - ids.length),
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
installDir,
|
|
168
|
+
total: wallpapers.length,
|
|
169
|
+
portableCount: wallpapers.filter((w) => w.playable).length,
|
|
170
|
+
wallpapers,
|
|
171
|
+
playlists: playlistView,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getInventory(force) {
|
|
176
|
+
if (!force && cache && Date.now() - cache.at < INVENTORY_TTL_MS) return cache.payload;
|
|
177
|
+
const payload = buildInventory();
|
|
178
|
+
cache = { at: Date.now(), payload };
|
|
179
|
+
return payload;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Response helpers ────────────────────────────────────────────────────
|
|
183
|
+
function sendJson(res, status, body) {
|
|
184
|
+
res.statusCode = status;
|
|
185
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
186
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
187
|
+
res.end(JSON.stringify(body));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Stream one file with Range support (video seeking) and HEAD handling. */
|
|
191
|
+
function serveFile(absPath, req, res) {
|
|
192
|
+
let st;
|
|
193
|
+
try { st = statSync(absPath); } catch { st = null; }
|
|
194
|
+
if (!st || !st.isFile()) {
|
|
195
|
+
res.statusCode = 404;
|
|
196
|
+
res.end('not found');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
res.setHeader('Content-Type', core.mimeFor(absPath));
|
|
200
|
+
res.setHeader('Accept-Ranges', 'bytes');
|
|
201
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
202
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
203
|
+
|
|
204
|
+
const range = core.parseRangeHeader(req.headers.range, st.size);
|
|
205
|
+
if (range && range.error) {
|
|
206
|
+
res.statusCode = 416;
|
|
207
|
+
res.setHeader('Content-Range', `bytes */${st.size}`);
|
|
208
|
+
res.end();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const head = req.method === 'HEAD';
|
|
212
|
+
if (range) {
|
|
213
|
+
res.statusCode = 206;
|
|
214
|
+
res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${st.size}`);
|
|
215
|
+
res.setHeader('Content-Length', String(range.end - range.start + 1));
|
|
216
|
+
} else {
|
|
217
|
+
res.setHeader('Content-Length', String(st.size));
|
|
218
|
+
}
|
|
219
|
+
if (head) { res.end(); return; }
|
|
220
|
+
|
|
221
|
+
const stream = range
|
|
222
|
+
? createReadStream(absPath, { start: range.start, end: range.end })
|
|
223
|
+
: createReadStream(absPath);
|
|
224
|
+
stream.on('error', () => {
|
|
225
|
+
if (!res.headersSent) res.statusCode = 500;
|
|
226
|
+
res.end();
|
|
227
|
+
});
|
|
228
|
+
stream.pipe(res);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── Routes ──────────────────────────────────────────────────────────────
|
|
232
|
+
const disposers = [];
|
|
233
|
+
|
|
234
|
+
disposers.push(webServer.register({
|
|
235
|
+
kind: 'exact',
|
|
236
|
+
path: `${BASE}/inventory`,
|
|
237
|
+
handler: (req, res) => {
|
|
238
|
+
if (req.method !== 'GET') { sendJson(res, 405, { error: 'method not allowed' }); return; }
|
|
239
|
+
try {
|
|
240
|
+
const url = new URL(req.url || '/', 'http://x');
|
|
241
|
+
sendJson(res, 200, getInventory(url.searchParams.get('refresh') === '1'));
|
|
242
|
+
} catch (err) {
|
|
243
|
+
sendJson(res, 500, { error: String(err && err.message ? err.message : err) });
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
}));
|
|
247
|
+
|
|
248
|
+
for (const seg of ['media', 'preview']) {
|
|
249
|
+
const prefix = `${BASE}/${seg}/`;
|
|
250
|
+
disposers.push(webServer.register({
|
|
251
|
+
kind: 'prefix',
|
|
252
|
+
path: `${BASE}/${seg}`,
|
|
253
|
+
handler: (req, res) => {
|
|
254
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
255
|
+
sendJson(res, 405, { error: 'method not allowed' });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
let pathname;
|
|
259
|
+
try {
|
|
260
|
+
pathname = new URL(req.url || '/', 'http://x').pathname;
|
|
261
|
+
} catch {
|
|
262
|
+
sendJson(res, 400, { error: 'bad request' });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const rest = pathname.slice(prefix.length); // "<token>" | "<token>/<rel…>"
|
|
266
|
+
const slash = rest.indexOf('/');
|
|
267
|
+
const rawToken = slash < 0 ? rest : rest.slice(0, slash);
|
|
268
|
+
let token;
|
|
269
|
+
try { token = decodeURIComponent(rawToken); } catch {
|
|
270
|
+
sendJson(res, 400, { error: 'bad token' });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const entry = tokens.get(token);
|
|
274
|
+
if (!entry) {
|
|
275
|
+
res.statusCode = 404;
|
|
276
|
+
res.end('not found');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
let abs = entry.abs;
|
|
280
|
+
if (slash >= 0) {
|
|
281
|
+
// Sub-asset of a bundled web wallpaper: must stay inside its dir.
|
|
282
|
+
if (seg !== 'media' || !entry.rootDir) {
|
|
283
|
+
res.statusCode = 404;
|
|
284
|
+
res.end('not found');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
let rel;
|
|
288
|
+
try { rel = decodeURIComponent(rest.slice(slash + 1)); } catch {
|
|
289
|
+
sendJson(res, 400, { error: 'bad path' });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!rel || rel.split('/').some((part) => part === '..' || part === '')) {
|
|
293
|
+
sendJson(res, 403, { error: 'forbidden' });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
abs = join(entry.rootDir, ...rel.split('/'));
|
|
297
|
+
if (!core.isInsideDir(entry.rootDir, abs)) {
|
|
298
|
+
sendJson(res, 403, { error: 'forbidden' });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
serveFile(abs, req, res);
|
|
303
|
+
},
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return () => {
|
|
308
|
+
for (const d of disposers) { try { d(); } catch { /* ignore */ } }
|
|
309
|
+
disposers.length = 0;
|
|
310
|
+
tokens.clear();
|
|
311
|
+
cache = null;
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export default { inject, apply };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wallpaper-engine-dsh — client-half type surface.
|
|
3
|
+
* Loaded by the DSH web module loader; consumes the slots service.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ClientContext {
|
|
7
|
+
slots?: {
|
|
8
|
+
inject(key: string, callback: () => unknown): unknown;
|
|
9
|
+
register(
|
|
10
|
+
options: { name: string; id: string; order?: number; label?: string },
|
|
11
|
+
render: () => unknown,
|
|
12
|
+
): unknown;
|
|
13
|
+
};
|
|
14
|
+
effect?(fn: () => void | (() => void)): unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export declare const inject: string[];
|
|
18
|
+
export declare function apply(ctx: ClientContext): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wallpaper-engine-dsh — host-half type surface.
|
|
3
|
+
* The runtime contract is a Cordis plugin module: named `inject` + `apply`,
|
|
4
|
+
* mirrored on the default export.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface WallpaperSummary {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
type: 'scene' | 'video' | 'web' | 'application';
|
|
11
|
+
playable: boolean;
|
|
12
|
+
media: string | null;
|
|
13
|
+
preview: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PlaylistSummary {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
order: 'sequence' | 'random';
|
|
20
|
+
delay: number | null;
|
|
21
|
+
wallpaperIds: string[];
|
|
22
|
+
total: number;
|
|
23
|
+
portableCount: number;
|
|
24
|
+
unresolvedCount: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Inventory {
|
|
28
|
+
installDir: string | null;
|
|
29
|
+
total: number;
|
|
30
|
+
portableCount: number;
|
|
31
|
+
wallpapers: WallpaperSummary[];
|
|
32
|
+
playlists: PlaylistSummary[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Cordis context shape this plugin consumes (structural, minimal). */
|
|
36
|
+
export interface HostContext {
|
|
37
|
+
webServer?: {
|
|
38
|
+
register(route: {
|
|
39
|
+
kind: 'exact' | 'prefix';
|
|
40
|
+
path: string;
|
|
41
|
+
handler(req: unknown, res: unknown): void | Promise<void>;
|
|
42
|
+
}): () => void;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export declare const inject: string[];
|
|
47
|
+
export declare function apply(ctx: HostContext): () => void;
|
|
48
|
+
|
|
49
|
+
declare const plugin: { inject: string[]; apply: typeof apply };
|
|
50
|
+
export default plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wallpaper-engine-dsh",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Wallpaper Engine Video/Web wallpapers as the DSH web GUI background — discovery, sandboxed rendering, rotation lists, liquid-glass tuning.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Weilv-D/wallpaper-engine-dsh.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"dsh",
|
|
14
|
+
"dsh-plugin",
|
|
15
|
+
"deepseek-harness",
|
|
16
|
+
"wallpaper",
|
|
17
|
+
"wallpaper-engine",
|
|
18
|
+
"background",
|
|
19
|
+
"theme"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./lib/types/index.d.ts",
|
|
27
|
+
"default": "./lib/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./client": {
|
|
30
|
+
"types": "./lib/types/client.d.ts",
|
|
31
|
+
"default": "./lib/client.js"
|
|
32
|
+
},
|
|
33
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"lib/core.js",
|
|
38
|
+
"lib/index.js",
|
|
39
|
+
"lib/client.js",
|
|
40
|
+
"lib/types/index.d.ts",
|
|
41
|
+
"lib/types/client.d.ts",
|
|
42
|
+
"cordis.patch.yml",
|
|
43
|
+
"README.md",
|
|
44
|
+
"README.zh.md"
|
|
45
|
+
],
|
|
46
|
+
"dsh": {
|
|
47
|
+
"bundle": {
|
|
48
|
+
"patch": "./cordis.patch.yml"
|
|
49
|
+
},
|
|
50
|
+
"client": {
|
|
51
|
+
"inject": [
|
|
52
|
+
"@deepseek-ai/dsh-client-runtime"
|
|
53
|
+
],
|
|
54
|
+
"platform": "web",
|
|
55
|
+
"immediately": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "node scripts/build-client.mjs",
|
|
61
|
+
"verify": "node scripts/verify-client.mjs",
|
|
62
|
+
"test": "node --test test/core.test.mjs",
|
|
63
|
+
"prepare": "node scripts/prepare.mjs",
|
|
64
|
+
"prepublishOnly": "npm run test && npm run build && npm run verify"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=18.17"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
71
|
+
"@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.6",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.6",
|
|
73
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.6",
|
|
74
|
+
"react": "^18.2.0"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"@deepseek-ai/dsh-host-webserver": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|