dsh-arknights-theme 1.0.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/ICON-ATTRIBUTION.md +45 -0
- package/LICENSE +28 -0
- package/README.md +283 -0
- package/assets/0d555849af53296a423101fb2319e4d3-narrow.webp +0 -0
- package/assets/0d555849af53296a423101fb2319e4d3-preview.webp +0 -0
- package/assets/0d555849af53296a423101fb2319e4d3-wide.webp +0 -0
- package/assets/1aa5e564d740a65d2d7a12164e9a826f-narrow.webp +0 -0
- package/assets/1aa5e564d740a65d2d7a12164e9a826f-preview.webp +0 -0
- package/assets/1aa5e564d740a65d2d7a12164e9a826f-wide.webp +0 -0
- package/assets/411e9b905a14497996bca303cf8429e6-narrow.webp +0 -0
- package/assets/411e9b905a14497996bca303cf8429e6-preview.webp +0 -0
- package/assets/411e9b905a14497996bca303cf8429e6-wide.webp +0 -0
- package/assets/7edeaee2037cd33e39f4bb1f1e358aab-narrow.webp +0 -0
- package/assets/7edeaee2037cd33e39f4bb1f1e358aab-preview.webp +0 -0
- package/assets/7edeaee2037cd33e39f4bb1f1e358aab-wide.webp +0 -0
- package/assets/832d435484f5905093721d315bb60134-narrow.webp +0 -0
- package/assets/832d435484f5905093721d315bb60134-preview.webp +0 -0
- package/assets/832d435484f5905093721d315bb60134-wide.webp +0 -0
- package/assets/83d33525d0e9dbdf83dbf7be5844b51c-narrow.webp +0 -0
- package/assets/83d33525d0e9dbdf83dbf7be5844b51c-preview.webp +0 -0
- package/assets/83d33525d0e9dbdf83dbf7be5844b51c-wide.webp +0 -0
- package/assets/87a14beae58e6309e52915cc62b9cd0b-narrow.webp +0 -0
- package/assets/87a14beae58e6309e52915cc62b9cd0b-preview.webp +0 -0
- package/assets/87a14beae58e6309e52915cc62b9cd0b-wide.webp +0 -0
- package/assets/8af3a3cc2360a8c74e9809fb5281a644-narrow.webp +0 -0
- package/assets/8af3a3cc2360a8c74e9809fb5281a644-preview.webp +0 -0
- package/assets/8af3a3cc2360a8c74e9809fb5281a644-wide.webp +0 -0
- package/assets/90164de0952ef8a66f46b0c1d9e19cb0-narrow.webp +0 -0
- package/assets/90164de0952ef8a66f46b0c1d9e19cb0-preview.webp +0 -0
- package/assets/90164de0952ef8a66f46b0c1d9e19cb0-wide.webp +0 -0
- package/assets/a897ddc19e122d7cb0ba70505db073fe-narrow.webp +0 -0
- package/assets/a897ddc19e122d7cb0ba70505db073fe-preview.webp +0 -0
- package/assets/a897ddc19e122d7cb0ba70505db073fe-wide.webp +0 -0
- package/assets/c90ef1248afbcba5f2034afedd601a05-narrow.webp +0 -0
- package/assets/c90ef1248afbcba5f2034afedd601a05-preview.webp +0 -0
- package/assets/c90ef1248afbcba5f2034afedd601a05-wide.webp +0 -0
- package/assets/d03aad3af10c655b80e37c3fe5dec298-narrow.webp +0 -0
- package/assets/d03aad3af10c655b80e37c3fe5dec298-preview.webp +0 -0
- package/assets/d03aad3af10c655b80e37c3fe5dec298-wide.webp +0 -0
- package/assets/preview-data.json +4874 -0
- package/assets/signatures/doctor.png +0 -0
- package/assets/signatures/kaltsit.png +0 -0
- package/assets/signatures/theresa.png +0 -0
- package/assets/skin.css +1749 -0
- package/assets/wallpapers.json +775 -0
- package/cordis.patch.yml +6 -0
- package/icons/folder-dossier-active.svg +6 -0
- package/icons/folder-dossier.svg +5 -0
- package/icons/hazard-rule.svg +4 -0
- package/icons/panel-collapse.svg +6 -0
- package/icons/panel-expand.svg +6 -0
- package/icons/perm-full.svg +5 -0
- package/icons/perm-readonly.svg +5 -0
- package/icons/perm-workspace.svg +4 -0
- package/icons/wallpaper-step.svg +5 -0
- package/lib/client.js +6219 -0
- package/lib/index.js +330 -0
- package/package.json +66 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-amiya-wallpaper — host half.
|
|
3
|
+
*
|
|
4
|
+
* The wallpaper skin itself lives in the browser half (`exports["./client"]`).
|
|
5
|
+
* This half exists for one reason: the generated wallpaper assets are several
|
|
6
|
+
* megabytes of WebP, far too much to inline into a client bundle, so the host
|
|
7
|
+
* must serve them. It registers a single read-only prefix route on the browser
|
|
8
|
+
* HTTP carrier and owns nothing else.
|
|
9
|
+
*
|
|
10
|
+
* Route surface (`/amiya-wallpaper`):
|
|
11
|
+
* GET /amiya-wallpaper/ -> assets/wallpapers.json
|
|
12
|
+
* GET /amiya-wallpaper/<file>.webp -> assets/<file>.webp
|
|
13
|
+
* GET /amiya-wallpaper/icons/<n>.svg -> icons/<n>.svg
|
|
14
|
+
*
|
|
15
|
+
* Responses are content-addressed by generation time, so they are served
|
|
16
|
+
* `immutable` with a long max-age: the asset set only changes when the import
|
|
17
|
+
* module is re-run and the bundle is reinstalled.
|
|
18
|
+
*
|
|
19
|
+
* Security: the route serves exactly two directories and rejects any request
|
|
20
|
+
* whose decoded path escapes them, so a traversal segment cannot read outside
|
|
21
|
+
* the package. Only GET and HEAD are answered.
|
|
22
|
+
*/
|
|
23
|
+
import os from 'node:os'
|
|
24
|
+
import { readFile, stat, writeFile } from 'node:fs/promises'
|
|
25
|
+
import { tmpdir } from 'node:os'
|
|
26
|
+
import path from 'node:path'
|
|
27
|
+
import { fileURLToPath } from 'node:url'
|
|
28
|
+
|
|
29
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url))
|
|
30
|
+
const PACKAGE_ROOT = path.resolve(HERE, '..')
|
|
31
|
+
|
|
32
|
+
/** Directory of generated wallpaper assets (`wallpapers.json` + WebP files). */
|
|
33
|
+
const ASSET_ROOT = path.join(PACKAGE_ROOT, 'assets')
|
|
34
|
+
/** Directory of authored SVG glyphs. */
|
|
35
|
+
const ICON_ROOT = path.join(PACKAGE_ROOT, 'icons')
|
|
36
|
+
|
|
37
|
+
/** Public route prefix; the client half builds its URLs from the same value. */
|
|
38
|
+
export const ROUTE = '/amiya-wallpaper'
|
|
39
|
+
|
|
40
|
+
const MIME = new Map([
|
|
41
|
+
['.webp', 'image/webp'],
|
|
42
|
+
['.svg', 'image/svg+xml'],
|
|
43
|
+
['.json', 'application/json; charset=utf-8'],
|
|
44
|
+
['.jpg', 'image/jpeg'],
|
|
45
|
+
['.jpeg', 'image/jpeg'],
|
|
46
|
+
['.png', 'image/png'],
|
|
47
|
+
['.avif', 'image/avif'],
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
/** One year; the asset set is regenerated only by a reinstall. */
|
|
51
|
+
const MAX_AGE_SECONDS = 31_536_000
|
|
52
|
+
|
|
53
|
+
/** Cordis hard dependency: with no HTTP carrier there is nothing to serve. */
|
|
54
|
+
export const inject = ['webServer']
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Where this plugin reports its own startup outcome. Outside the package on
|
|
58
|
+
* purpose: it is a diagnostic, not an asset, and writing it must never dirty an
|
|
59
|
+
* installation.
|
|
60
|
+
*/
|
|
61
|
+
const STATUS_FILE = path.join(tmpdir(), 'dsh-amiya-wallpaper-host.json')
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Host plugin body: register the asset route for exactly this fiber's lifetime,
|
|
65
|
+
* then record whether that worked.
|
|
66
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx
|
|
67
|
+
*/
|
|
68
|
+
export function apply(ctx) {
|
|
69
|
+
let registered = false
|
|
70
|
+
let error
|
|
71
|
+
|
|
72
|
+
/*
|
|
73
|
+
* Reach the browser through index.html rather than through the client module
|
|
74
|
+
* graph. Two reasons, both learned the hard way (the mount contract follows the
|
|
75
|
+
* beautiCode DSH bridge):
|
|
76
|
+
*
|
|
77
|
+
* * the stylesheet arrives as a plain <link>, so it applies before the first
|
|
78
|
+
* paint and does not depend on a client bundle being snapshotted, served,
|
|
79
|
+
* and executed in the right order;
|
|
80
|
+
* * the attribute is set by a blocking inline script, so every rule in the
|
|
81
|
+
* stylesheet — all of which are scoped to it — is live from the first frame,
|
|
82
|
+
* and the stylesheet stays inert in a DSH that is not running this skin.
|
|
83
|
+
*/
|
|
84
|
+
ctx.effect(() => {
|
|
85
|
+
const disposeTap = ctx.webServer.tapIndex((html) => {
|
|
86
|
+
if (html.includes('data-amiya-skin=')) return html
|
|
87
|
+
const head =
|
|
88
|
+
'<link rel="stylesheet" href="' +
|
|
89
|
+
ROUTE +
|
|
90
|
+
'/assets/skin.css" data-amiya-skin="1">' +
|
|
91
|
+
'<script data-amiya-skin="1">document.documentElement.setAttribute("data-amiya-skin","1")</script>'
|
|
92
|
+
return html.includes('</head>')
|
|
93
|
+
? html.replace('</head>', head + '</head>')
|
|
94
|
+
: head + html
|
|
95
|
+
})
|
|
96
|
+
return disposeTap
|
|
97
|
+
}, 'amiya-wallpaper: index injection')
|
|
98
|
+
|
|
99
|
+
ctx.effect(() => {
|
|
100
|
+
try {
|
|
101
|
+
const dispose = ctx.webServer.register({
|
|
102
|
+
kind: 'prefix',
|
|
103
|
+
path: ROUTE,
|
|
104
|
+
handler: (req, res) => {
|
|
105
|
+
void respond(req, res)
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
registered = true
|
|
109
|
+
return dispose
|
|
110
|
+
} catch (cause) {
|
|
111
|
+
/*
|
|
112
|
+
* A route pattern is a composition-level contract, so a second owner of
|
|
113
|
+
* this path throws. The realistic case is a live preview of this same
|
|
114
|
+
* skin mounted beside the installed plugin: both serve identical bytes
|
|
115
|
+
* from the same directory, so the right move is to log it and let the
|
|
116
|
+
* existing owner answer rather than fail this fiber.
|
|
117
|
+
*/
|
|
118
|
+
error = cause instanceof Error ? cause.message : String(cause)
|
|
119
|
+
console.warn(
|
|
120
|
+
`[dsh-amiya-wallpaper] ${ROUTE} is already served by another owner; ` +
|
|
121
|
+
`leaving the route alone (${error})`,
|
|
122
|
+
)
|
|
123
|
+
return () => {}
|
|
124
|
+
}
|
|
125
|
+
}, 'amiya-wallpaper: asset route')
|
|
126
|
+
|
|
127
|
+
/*
|
|
128
|
+
* A host half that silently fails to register looks exactly like a host half
|
|
129
|
+
* with missing assets: the client half still styles the interface, but every
|
|
130
|
+
* wallpaper URL 404s — a skin with no wallpaper. The harness keeps no log this
|
|
131
|
+
* toolchain can read, so the outcome is written where the toolchain can look.
|
|
132
|
+
*/
|
|
133
|
+
void writeStatus({ registered, error: error ?? null, packageRoot: PACKAGE_ROOT, assetRoot: ASSET_ROOT })
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Record the startup outcome, best effort.
|
|
138
|
+
* @param {object} detail
|
|
139
|
+
*/
|
|
140
|
+
async function writeStatus(detail) {
|
|
141
|
+
try {
|
|
142
|
+
await writeFile(
|
|
143
|
+
STATUS_FILE,
|
|
144
|
+
`${JSON.stringify({ at: new Date().toISOString(), route: ROUTE, ...detail }, null, 2)}\n`,
|
|
145
|
+
'utf8',
|
|
146
|
+
)
|
|
147
|
+
} catch {
|
|
148
|
+
/* A read-only temp directory is not a reason to fail a fiber. */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Where the browser half's layout report lands. */
|
|
153
|
+
const DIAG_FILE = path.join(os.tmpdir(), 'dsh-amiya-wallpaper-diag.json')
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Read a request body up to `limit` bytes.
|
|
157
|
+
* @param {import('node:http').IncomingMessage} req
|
|
158
|
+
* @param {number} limit
|
|
159
|
+
*/
|
|
160
|
+
async function readBody(req, limit) {
|
|
161
|
+
const chunks = []
|
|
162
|
+
let size = 0
|
|
163
|
+
for await (const chunk of req) {
|
|
164
|
+
size += chunk.length
|
|
165
|
+
if (size > limit) throw new Error('body too large')
|
|
166
|
+
chunks.push(chunk)
|
|
167
|
+
}
|
|
168
|
+
return Buffer.concat(chunks).toString('utf8')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Answer one request from the asset or icon directory.
|
|
173
|
+
* @param {import('node:http').IncomingMessage} req
|
|
174
|
+
* @param {import('node:http').ServerResponse} res
|
|
175
|
+
*/
|
|
176
|
+
async function respond(req, res) {
|
|
177
|
+
const method = req.method ?? 'GET'
|
|
178
|
+
|
|
179
|
+
/* Diagnostics channel, written by the browser half: it POSTs a measurement of the
|
|
180
|
+
settings dialog and GET returns the last one. Kept on its own path so the read-only
|
|
181
|
+
asset route stays exactly as strict as it was for everything else. */
|
|
182
|
+
try {
|
|
183
|
+
const diagPath = decodePath(req.url ?? '')
|
|
184
|
+
if (diagPath === 'diag') {
|
|
185
|
+
if (method === 'POST') {
|
|
186
|
+
const body = await readBody(req, 512 * 1024)
|
|
187
|
+
/* Append rather than overwrite: one capture per screen the reader visits. */
|
|
188
|
+
let captures = []
|
|
189
|
+
try {
|
|
190
|
+
const previous = JSON.parse(await readFile(DIAG_FILE, 'utf8'))
|
|
191
|
+
if (Array.isArray(previous.captures)) captures = previous.captures
|
|
192
|
+
} catch {
|
|
193
|
+
/* first capture */
|
|
194
|
+
}
|
|
195
|
+
let capture
|
|
196
|
+
try {
|
|
197
|
+
capture = JSON.parse(body)
|
|
198
|
+
} catch {
|
|
199
|
+
capture = { unparsed: body.slice(0, 4000) }
|
|
200
|
+
}
|
|
201
|
+
captures.push(capture)
|
|
202
|
+
if (captures.length > 20) captures = captures.slice(-20)
|
|
203
|
+
await writeFile(
|
|
204
|
+
DIAG_FILE,
|
|
205
|
+
JSON.stringify({ updatedAt: new Date().toISOString(), captures }, null, 1),
|
|
206
|
+
'utf8',
|
|
207
|
+
)
|
|
208
|
+
send(res, 200, 'text/plain; charset=utf-8', 'ok ' + captures.length)
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const stored = await readFile(DIAG_FILE, 'utf8')
|
|
213
|
+
send(res, 200, 'application/json; charset=utf-8', stored)
|
|
214
|
+
} catch {
|
|
215
|
+
send(res, 404, 'text/plain; charset=utf-8', 'no report yet')
|
|
216
|
+
}
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
/* fall through to the asset route */
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (method !== 'GET' && method !== 'HEAD') {
|
|
224
|
+
res.writeHead(405, { allow: 'GET, HEAD' })
|
|
225
|
+
res.end()
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let relative
|
|
230
|
+
try {
|
|
231
|
+
relative = decodePath(req.url ?? '')
|
|
232
|
+
} catch {
|
|
233
|
+
send(res, 400, 'text/plain; charset=utf-8', 'bad request path')
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const target = resolveTarget(relative)
|
|
238
|
+
if (target === undefined) {
|
|
239
|
+
send(res, 404, 'text/plain; charset=utf-8', 'not found')
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let body
|
|
244
|
+
try {
|
|
245
|
+
const info = await stat(target)
|
|
246
|
+
if (!info.isFile()) throw new Error('not a file')
|
|
247
|
+
body = await readFile(target)
|
|
248
|
+
} catch {
|
|
249
|
+
send(res, 404, 'text/plain; charset=utf-8', 'not found')
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const type = MIME.get(path.extname(target).toLowerCase()) ?? 'application/octet-stream'
|
|
254
|
+
/* Stylesheet and manifest are re-read per request and must not be cached: they
|
|
255
|
+
are how a fix lands on a page reload without restarting DSH. The images are
|
|
256
|
+
content-stable and are cached hard. */
|
|
257
|
+
const cacheable = type !== 'text/css; charset=utf-8' && type !== 'application/json; charset=utf-8'
|
|
258
|
+
const headers = {
|
|
259
|
+
'content-type': type,
|
|
260
|
+
'cache-control': cacheable ? `public, max-age=${MAX_AGE_SECONDS}, immutable` : 'no-store',
|
|
261
|
+
'content-length': String(body.length),
|
|
262
|
+
}
|
|
263
|
+
res.writeHead(200, headers)
|
|
264
|
+
if (method === 'HEAD') res.end()
|
|
265
|
+
else res.end(body)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Decode a request URL into a package-relative path below the route prefix.
|
|
270
|
+
* @param {string} url
|
|
271
|
+
* @returns {string} e.g. `''`, `foo-wide.webp`, or `icons/amiya-mark.svg`
|
|
272
|
+
*/
|
|
273
|
+
function decodePath(url) {
|
|
274
|
+
const withoutQuery = url.split('?')[0].split('#')[0]
|
|
275
|
+
const decoded = decodeURIComponent(withoutQuery)
|
|
276
|
+
const rest = decoded.startsWith(ROUTE) ? decoded.slice(ROUTE.length) : decoded
|
|
277
|
+
return rest.replace(/^\/+/, '')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Map a relative path onto a file inside the two served directories.
|
|
282
|
+
*
|
|
283
|
+
* Three shapes are accepted, and nothing else: the manifest, a bare wallpaper
|
|
284
|
+
* file name, or a name under `assets/` or `icons/`. Anything containing a path
|
|
285
|
+
* separator beyond that known prefix, a parent link, or a NUL is refused — which
|
|
286
|
+
* is what blocks traversal and absolute paths.
|
|
287
|
+
*
|
|
288
|
+
* @param {string} relative
|
|
289
|
+
* @returns {string | undefined}
|
|
290
|
+
*/
|
|
291
|
+
function resolveTarget(relative) {
|
|
292
|
+
if (relative === '') return path.join(ASSET_ROOT, 'wallpapers.json')
|
|
293
|
+
|
|
294
|
+
const directories = [
|
|
295
|
+
{ prefix: 'assets/', root: ASSET_ROOT },
|
|
296
|
+
{ prefix: 'icons/', root: ICON_ROOT },
|
|
297
|
+
]
|
|
298
|
+
for (const { prefix, root } of directories) {
|
|
299
|
+
if (!relative.startsWith(prefix)) continue
|
|
300
|
+
return safeJoin(root, relative.slice(prefix.length))
|
|
301
|
+
}
|
|
302
|
+
return safeJoin(ASSET_ROOT, relative)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Join one flat file name under `root`, refusing anything that could escape it.
|
|
307
|
+
* @param {string} root
|
|
308
|
+
* @param {string} name
|
|
309
|
+
* @returns {string | undefined}
|
|
310
|
+
*/
|
|
311
|
+
function safeJoin(root, name) {
|
|
312
|
+
if (name === '' || name.includes('\0')) return undefined
|
|
313
|
+
if (name.includes('/') || name.includes('\\')) return undefined
|
|
314
|
+
if (name === '.' || name === '..') return undefined
|
|
315
|
+
const resolved = path.resolve(root, name)
|
|
316
|
+
const prefix = root.endsWith(path.sep) ? root : root + path.sep
|
|
317
|
+
return resolved.startsWith(prefix) ? resolved : undefined
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Write a small plain-text response.
|
|
322
|
+
* @param {import('node:http').ServerResponse} res
|
|
323
|
+
* @param {number} status
|
|
324
|
+
* @param {string} type
|
|
325
|
+
* @param {string} body
|
|
326
|
+
*/
|
|
327
|
+
function send(res, status, type, body) {
|
|
328
|
+
res.writeHead(status, { 'content-type': type, 'content-length': String(Buffer.byteLength(body)) })
|
|
329
|
+
res.end(body)
|
|
330
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-arknights-theme",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "为《明日方舟》玩家在 DSH Web 上构建沉浸感:12 张整屏壁纸(按画面自适应的边缘虚化)、按每张图解算的玻璃与文字色、罗德岛图标体系,以及签字式的授权与选择交互。 / Building immersion for Arknights players on the DSH Web GUI: twelve full-screen wallpapers with per-image edge blur, glass and ink solved from each picture, Rhodes Island iconography, and sign-off style approval and choice interactions.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"icon": "./icons/amiya-mark.svg",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"default": "./lib/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./client": "./lib/client.js",
|
|
13
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
14
|
+
"./wallpapers.json": "./assets/wallpapers.json",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"lib",
|
|
19
|
+
"assets/*.webp",
|
|
20
|
+
"assets/*.json",
|
|
21
|
+
"assets/*.css",
|
|
22
|
+
"assets/signatures",
|
|
23
|
+
"icons",
|
|
24
|
+
"cordis.patch.yml",
|
|
25
|
+
"README.md",
|
|
26
|
+
"ICON-ATTRIBUTION.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./cordis.patch.yml"
|
|
32
|
+
},
|
|
33
|
+
"client": {
|
|
34
|
+
"inject": [],
|
|
35
|
+
"platform": "web"
|
|
36
|
+
},
|
|
37
|
+
"manifestVersion": 1
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"deepseek-harness",
|
|
41
|
+
"dsh",
|
|
42
|
+
"dsh-plugin",
|
|
43
|
+
"arknights",
|
|
44
|
+
"rhodes-island",
|
|
45
|
+
"wallpaper",
|
|
46
|
+
"theme",
|
|
47
|
+
"immersion",
|
|
48
|
+
"acrylic"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@deepseek-ai/cordis": "^4.0.4"
|
|
55
|
+
},
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "git+https://github.com/Theflowyears/dsh-arknights-theme.git"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/Theflowyears/dsh-arknights-theme#readme",
|
|
62
|
+
"bugs": {
|
|
63
|
+
"url": "https://github.com/Theflowyears/dsh-arknights-theme/issues"
|
|
64
|
+
},
|
|
65
|
+
"author": "Theflowyears"
|
|
66
|
+
}
|