dsh-inner-browser-use 0.34.28 → 0.34.95
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 +53 -18
- package/README.zh.md +26 -14
- package/client.js +161 -22
- package/docs/ARCHITECTURE.md +15 -12
- package/docs/ARTIFACTS.md +1 -1
- package/docs/BENCH.md +209 -34
- package/docs/FLASH.md +84 -0
- package/docs/ROADMAP.md +21 -15
- package/docs/VERIFY.md +20 -10
- package/index.js +144 -24
- package/package.json +17 -3
- package/read.js +633 -29
- package/runtime/flash-app/main.js +232 -0
- package/runtime/flash-app/package.json +1 -0
- package/runtime/flash-setup.mjs +151 -0
- package/runtime/swf.mjs +233 -0
- package/scripts/check-flash-local.mjs +104 -0
- package/scripts/check-flash-mirror.mjs +73 -0
- package/scripts/check-flash-tools.mjs +74 -0
- package/scripts/check-flash-window.mjs +58 -0
- package/scripts/doctor.mjs +3 -1
- package/sidecar.js +1076 -96
- package/tools.js +1068 -144
package/docs/VERIFY.md
CHANGED
|
@@ -33,7 +33,7 @@ Expected before any tool call: `"alive":false`, `"profile":"default"`,
|
|
|
33
33
|
|
|
34
34
|
## 2. A tool opens its own page
|
|
35
35
|
|
|
36
|
-
Ask the model `用
|
|
36
|
+
Ask the model `用 browser_open 打开 https://www.bing.com`, or call it yourself.
|
|
37
37
|
Expected: the result names the address and title, and this chat's right column
|
|
38
38
|
shows the picture in a tab called **后台浏览器** or **Background browser**.
|
|
39
39
|
`doctor.mjs` now reads `attached, headless`, `viewers 1`, frames climbing.
|
|
@@ -43,8 +43,8 @@ shows the picture in a tab called **后台浏览器** or **Background browser**.
|
|
|
43
43
|
- `"alive":false` with a non-empty `"error"`: Chrome did not start. Check the
|
|
44
44
|
binary at a listed path, and on macOS that the launch carried
|
|
45
45
|
`--use-mock-keychain`.
|
|
46
|
-
- Frames flat while viewers is 1: the screencast stalled. `
|
|
47
|
-
`
|
|
46
|
+
- Frames flat while viewers is 1: the screencast stalled. `browser_stop`, then
|
|
47
|
+
`browser_open` again.
|
|
48
48
|
- `"pointer":null`: no tool input yet; it fills on the first click or move.
|
|
49
49
|
|
|
50
50
|
## 3. Only the chat that asked
|
|
@@ -60,18 +60,28 @@ A mirror that opens in every chat means the page half is not comparing
|
|
|
60
60
|
- **Hover** the search field: your cursor turns into an I-beam, the black arrow
|
|
61
61
|
does not move. An arrow that follows the mouse means the pointer paths are
|
|
62
62
|
crossed.
|
|
63
|
-
- **Type** Chinese with `
|
|
63
|
+
- **Type** Chinese with `browser_type { "text": "…", "submit": true }`: the
|
|
64
64
|
results page appears and the field holds characters, not boxes.
|
|
65
|
-
- **Backspace** with `
|
|
65
|
+
- **Backspace** with `browser_key { "key": "Backspace" }` in a filled field:
|
|
66
66
|
one character goes. Nothing happening means the key reached the document.
|
|
67
|
-
- **Screenshot** with `
|
|
67
|
+
- **Screenshot** with `browser_shot {}`: an absolute path under
|
|
68
68
|
`~/.dsh-inner-browser-use/screenshots`. A black picture means the screencast
|
|
69
69
|
was not running when the frame was taken.
|
|
70
|
+
- **The whole keyboard, on this machine**: `npm run keys` starts a real
|
|
71
|
+
background Chrome, presses all 150 keys into a field, and checks what the page
|
|
72
|
+
reported for each one (`key`, `code`, `keyCode`, shift) plus what the field
|
|
73
|
+
typed. It then runs copy / cut / paste / select-all / undo / redo, caret keys,
|
|
74
|
+
Tab focus, Enter, and a 180 ms hold. One JSON line, exit code 0 when every key
|
|
75
|
+
matched. CI runs it on macOS and Windows in the `keys` job.
|
|
76
|
+
- **A shortcut by hand**: type `Hi`, press `browser_key { "key": "Mod+a" }`,
|
|
77
|
+
type `X`: the field holds `X` and nothing before it. `Mod` is Command on macOS
|
|
78
|
+
and Control elsewhere; `Ctrl+C/X/V/A/Z/Y` and `Ctrl+Shift+Z` run the same
|
|
79
|
+
editing commands a person would.
|
|
70
80
|
|
|
71
81
|
## 5. Stop, then open again
|
|
72
82
|
|
|
73
|
-
`
|
|
74
|
-
`
|
|
83
|
+
`browser_stop` reads `"alive":false` and leaves the profile alone.
|
|
84
|
+
`browser_open https://www.bing.com` brings the same profile back, still signed
|
|
75
85
|
in from the first pass.
|
|
76
86
|
|
|
77
87
|
Signed out after the restart: Chrome came up on another profile, or was killed
|
|
@@ -140,13 +150,13 @@ broke. Two of them cover a cross-site frame, which Chrome runs in its own
|
|
|
140
150
|
process. Three cover a control under an overlay: the click clears a cookie
|
|
141
151
|
banner and lands (`under clicked`), typing into a field under the same banner
|
|
142
152
|
works, and a modal that offers to delete the account is reported without
|
|
143
|
-
anything being pressed. Two more drive the same overlay through `
|
|
153
|
+
anything being pressed. Two more drive the same overlay through `browser_do`:
|
|
144
154
|
a batch step clears the banner before it clicks, and a batch step fills a field
|
|
145
155
|
that was under it. One more counts the recoveries: after the redraw check, the
|
|
146
156
|
state line says at least one ref was followed to its new number. Three cover a
|
|
147
157
|
hand on the mirror: while the person is using it a read still works
|
|
148
158
|
(`hold-allows-read`), an action is refused and names both ways out
|
|
149
|
-
(`hold-refuses-action`), and `
|
|
159
|
+
(`hold-refuses-action`), and `browser_resume` hands the page back so the same
|
|
150
160
|
click goes through (`resume-lets-go`).
|
|
151
161
|
|
|
152
162
|
## Who the mirror serves
|
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* learns no port and no secret. // ui-copy-ignore
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
23
|
+
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
24
24
|
import { homedir } from 'node:os'
|
|
25
25
|
import { join } from 'node:path'
|
|
26
26
|
import { Sidecar } from './sidecar.js?v=1'
|
|
@@ -106,6 +106,17 @@ export class Bridge {
|
|
|
106
106
|
this.wanted = { session: kept.session, at: kept.at }
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* A chat is the chat that asked, and nothing else. Guessing at an alias once
|
|
111
|
+
* put the mirror in a stranger's column: two conversations are never the same
|
|
112
|
+
* conversation, however few pages happen to be on screen.
|
|
113
|
+
* @param {string} session - whatever id the caller has.
|
|
114
|
+
* @returns {string} the same id.
|
|
115
|
+
*/
|
|
116
|
+
canon(session) {
|
|
117
|
+
return String(session ?? '')
|
|
118
|
+
}
|
|
119
|
+
|
|
109
120
|
/**
|
|
110
121
|
* Give the mirror to one chat and take it away from every earlier one.
|
|
111
122
|
* @param {string} sessionId - chat that asked for it.
|
|
@@ -123,11 +134,12 @@ export class Bridge {
|
|
|
123
134
|
* @returns {boolean} whether a live tab took the frame.
|
|
124
135
|
*/
|
|
125
136
|
want(sessionId, at = Date.now()) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
|
|
137
|
+
const id = this.canon(sessionId)
|
|
138
|
+
if (id === '') return false
|
|
139
|
+
this.claim(id)
|
|
140
|
+
this.wanted = { session: id, at }
|
|
141
|
+
if (this.persist === true) writeMirror({ owner: this.owner, session: id, at })
|
|
142
|
+
return this.push(id, { type: 'sidecar-wanted', session: id, at })
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
/**
|
|
@@ -195,7 +207,7 @@ export class Bridge {
|
|
|
195
207
|
/** Page read: address, title, controls, then text, as JSON the Host parses. */ // ui-copy-ignore
|
|
196
208
|
/* The read model and the tool surface live in their own files: this module is
|
|
197
209
|
the Host half (routes, bridge, lifetimes), not the model's vocabulary. */ // ui-copy-ignore
|
|
198
|
-
import {
|
|
210
|
+
import { keyEvent, sidecarTools, sessionScope, workScope } from './tools.js?v=1'
|
|
199
211
|
|
|
200
212
|
/**
|
|
201
213
|
* A state poll may pick up a browser that outlived a reload, and only for the
|
|
@@ -252,6 +264,36 @@ export function shouldAdoptOnState(said, chat, owner) {
|
|
|
252
264
|
return said?.alive === false && chat !== '' && owner === chat
|
|
253
265
|
}
|
|
254
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Whether a running browser still needs its column told to draw it.
|
|
269
|
+
* @param {{alive: boolean, owner: string, wanted: string, listeners: number}} what - current facts.
|
|
270
|
+
* @returns {boolean} true when the owner is known, the browser runs, and nothing listens.
|
|
271
|
+
*/
|
|
272
|
+
export function wantsMirrorAgain(what) {
|
|
273
|
+
if (what?.alive !== true) return false
|
|
274
|
+
const owner = String(what?.owner ?? '')
|
|
275
|
+
if (owner === '') return false
|
|
276
|
+
if (String(what?.wanted ?? '') !== owner) return false
|
|
277
|
+
return Number(what?.listeners ?? 0) <= 0
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Whether asking again can achieve anything. A pane that is polling this very
|
|
282
|
+
* endpoint is awake and already knows the frames are missing, so a second ask
|
|
283
|
+
* every few seconds only writes log lines; a pane that went quiet may have lost
|
|
284
|
+
* the stream and does need one. One ask per gap at most.
|
|
285
|
+
* @param {any} what - { askedAgo, sinceLast, quiet, gap } in milliseconds.
|
|
286
|
+
* @returns {boolean} true when the ask should be sent now.
|
|
287
|
+
*/
|
|
288
|
+
export function nudgeDue(what) {
|
|
289
|
+
const askedAgo = Number(what?.askedAgo ?? -1)
|
|
290
|
+
const sinceLast = Number(what?.sinceLast ?? Infinity)
|
|
291
|
+
const quiet = Number(what?.quiet ?? 15_000)
|
|
292
|
+
const gap = Number(what?.gap ?? 10_000)
|
|
293
|
+
if (!Number.isFinite(askedAgo) || askedAgo < quiet) return false
|
|
294
|
+
return sinceLast >= gap
|
|
295
|
+
}
|
|
296
|
+
|
|
255
297
|
export const name = 'dsh-inner-browser-use'
|
|
256
298
|
export const inject = ['webServer']
|
|
257
299
|
|
|
@@ -268,6 +310,31 @@ export function apply(ctx) {
|
|
|
268
310
|
/** @type {{refs: any[], at: number, last?: any}} */
|
|
269
311
|
const sidecarRefs = { refs: [], at: 0 }
|
|
270
312
|
ctx.effect(() => () => bridge.dispose(), 'dsh-inner-browser-use: bridge lifetime')
|
|
313
|
+
/* A live picture that nobody is drawing is the one failure people notice
|
|
314
|
+
first: the browser is fine, the column just lost its pane. The chat that
|
|
315
|
+
owns the browser is asked again, on a slow beat, until the frames stream
|
|
316
|
+
has a listener — the client keeps the rule that only the mounted chat's
|
|
317
|
+
column may open. */
|
|
318
|
+
/** @type {Map<string, number>} when each chat last polled for state. */
|
|
319
|
+
const lastAsk = new Map()
|
|
320
|
+
ctx.effect(() => {
|
|
321
|
+
let last = 0
|
|
322
|
+
const timer = setInterval(() => {
|
|
323
|
+
try {
|
|
324
|
+
const owner = bridge.owner
|
|
325
|
+
const listeners = sidecar.page?.frames?.size ?? 0
|
|
326
|
+
const again = wantsMirrorAgain({ alive: sidecar.page !== null && sidecar.page !== undefined, owner, wanted: bridge.wanted.session, listeners }) // ui-copy-ignore
|
|
327
|
+
if (again !== true) return
|
|
328
|
+
const due = nudgeDue({ askedAgo: Date.now() - (lastAsk.get(owner) ?? 0), sinceLast: Date.now() - last }) // ui-copy-ignore
|
|
329
|
+
if (due !== true) return
|
|
330
|
+
last = Date.now()
|
|
331
|
+
bridge.want(owner)
|
|
332
|
+
appendFileSync(join(STATE_DIR, 'asks.log'), `${new Date().toISOString()} asked ${owner.slice(-8)} to draw the mirror again\n`)
|
|
333
|
+
} catch {}
|
|
334
|
+
}, 4000)
|
|
335
|
+
timer.unref?.()
|
|
336
|
+
return () => clearInterval(timer)
|
|
337
|
+
}, 'dsh-inner-browser-use: mirror keepalive')
|
|
271
338
|
/* Closing the browser cleanly on quit is what keeps a sign-in alive: a
|
|
272
339
|
killed Chrome never writes the cookies a page set this minute. */
|
|
273
340
|
ctx.effect(() => {
|
|
@@ -285,6 +352,10 @@ export function apply(ctx) {
|
|
|
285
352
|
/** @param {import('node:http').IncomingMessage} req - request. @param {import('node:http').ServerResponse} res - response. */
|
|
286
353
|
/** @type {{at: number, path: string, session: string}[]} the last few calls the column made. */
|
|
287
354
|
const recent = []
|
|
355
|
+
/* Whether the pane is asking at all is the difference between "the browser
|
|
356
|
+
stopped" and "nothing is drawing it": the poll is every 2.5 s, so a chat
|
|
357
|
+
that asks is a chat with a mounted pane. Kept on disk because the answer
|
|
358
|
+
has to survive a plugin reload, which is when the question is asked. */
|
|
288
359
|
const handler = async (req, res) => {
|
|
289
360
|
const url = new URL(req.url ?? ROUTE, 'http://dsh.invalid')
|
|
290
361
|
const path = url.pathname.slice(ROUTE.length) || '/'
|
|
@@ -334,6 +405,8 @@ export function apply(ctx) {
|
|
|
334
405
|
const asked = String(url.searchParams.get('session') ?? '')
|
|
335
406
|
const watching = asked !== '' ? asked : sessionId
|
|
336
407
|
if (held(watching) && refuse()) return
|
|
408
|
+
/* Only the owner's own page may stream the picture. */
|
|
409
|
+
const streaming = watching
|
|
337
410
|
res.writeHead(200, {
|
|
338
411
|
'content-type': 'text/event-stream; charset=utf-8',
|
|
339
412
|
'cache-control': 'no-cache, no-transform',
|
|
@@ -353,7 +426,7 @@ export function apply(ctx) {
|
|
|
353
426
|
page wait: the newest picture is the only one worth sending. */
|
|
354
427
|
if (res.writableLength > 3 * 1024 * 1024) return
|
|
355
428
|
res.write(`data: ${JSON.stringify(frame)}\n\n`)
|
|
356
|
-
},
|
|
429
|
+
}, streaming)
|
|
357
430
|
const info = await sidecar.state()
|
|
358
431
|
/* The pane needs one address to offer back when this chat's own page
|
|
359
432
|
went away; the state route answers the same question for the tab
|
|
@@ -426,6 +499,15 @@ export function apply(ctx) {
|
|
|
426
499
|
said = await sidecar.state()
|
|
427
500
|
} catch {}
|
|
428
501
|
}
|
|
502
|
+
if (chat !== '') {
|
|
503
|
+
const before = lastAsk.get(chat) ?? 0
|
|
504
|
+
if (Date.now() - before > 2000) {
|
|
505
|
+
lastAsk.set(chat, Date.now())
|
|
506
|
+
try {
|
|
507
|
+
appendFileSync(join(STATE_DIR, 'asks.log'), `${new Date().toISOString()} ${chat.slice(-8)} alive=${String(said?.alive === true)} viewers=${String(said?.viewers ?? 0)}\n`)
|
|
508
|
+
} catch {}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
429
511
|
json(200, { ...said, ...(chat === '' ? {} : { chat: mineChat ?? (await sidecar.chatState(chat)) }), owner: bridge.owner, chats: [...bridge.seats.keys()], recent: mine })
|
|
430
512
|
return
|
|
431
513
|
}
|
|
@@ -463,6 +545,35 @@ export function apply(ctx) {
|
|
|
463
545
|
return
|
|
464
546
|
}
|
|
465
547
|
|
|
548
|
+
if (path === '/sidecar/viewport' && req.method === 'POST') {
|
|
549
|
+
/* The pane tells the page how big it is, so the picture fills the column
|
|
550
|
+
instead of being letterboxed inside it. Only the owner may resize what
|
|
551
|
+
its own mirror shows. */
|
|
552
|
+
let body
|
|
553
|
+
try {
|
|
554
|
+
body = JSON.parse(await readBody(req))
|
|
555
|
+
} catch {
|
|
556
|
+
json(400, { ok: false, text: 'bad frame' }) // ui-copy-ignore
|
|
557
|
+
return
|
|
558
|
+
}
|
|
559
|
+
const actor = acting(body)
|
|
560
|
+
if (actor === '') {
|
|
561
|
+
json(403, { ok: false, text: 'this chat does not own the browser' }) // ui-copy-ignore
|
|
562
|
+
return
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
const page = sidecar.page
|
|
566
|
+
if (page === null || page === undefined) {
|
|
567
|
+
json(409, { ok: false, text: 'the browser is not open' }) // ui-copy-ignore
|
|
568
|
+
return
|
|
569
|
+
}
|
|
570
|
+
json(200, { ok: true, view: await page.setViewport(body.w, body.h) })
|
|
571
|
+
} catch (error) {
|
|
572
|
+
json(500, { ok: false, text: String(error?.message ?? error) })
|
|
573
|
+
}
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
|
|
466
577
|
if (path === '/sidecar/input' && req.method === 'POST') {
|
|
467
578
|
let body
|
|
468
579
|
try {
|
|
@@ -495,21 +606,26 @@ export function apply(ctx) {
|
|
|
495
606
|
} else if (kind === 'wheel') await page.wheel({ x, y, dx: Number(body.dx ?? 0), dy: Number(body.dy ?? 0) })
|
|
496
607
|
else if (kind === 'text') await page.insertText(String(body.text ?? ''))
|
|
497
608
|
else if (kind === 'key') {
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
modifiers: Number(body.modifiers ?? 0),
|
|
506
|
-
}
|
|
507
|
-
await page.key('keyDown', spec)
|
|
508
|
-
await page.key('keyUp', { ...spec, text: undefined })
|
|
609
|
+
const key = keyEvent(body)
|
|
610
|
+
if (key.down !== true) await page.key('keyUp', { ...key.spec, text: undefined })
|
|
611
|
+
else if (key.repeat === true) {
|
|
612
|
+
/* Typing repeat: the page draws a held key as a fresh keydown. */
|
|
613
|
+
await page.key('keyDown', { ...key.spec, autoRepeat: true })
|
|
614
|
+
} else if (key.command === null) await page.key('keyDown', key.spec)
|
|
615
|
+
else await page.key('keyDown', { ...key.spec, text: undefined, commands: [key.command] })
|
|
509
616
|
}
|
|
510
617
|
/* Hovering and moving are looking, not acting: only real input counts as
|
|
511
618
|
the person taking the page. */
|
|
512
|
-
if (kind !== 'hover' && kind !== 'move')
|
|
619
|
+
if (kind !== 'hover' && kind !== 'move') {
|
|
620
|
+
sidecar.humanUse({ kind, x, y })
|
|
621
|
+
/* The arrow goes now, not on the next repaint: a still page may not
|
|
622
|
+
paint again for a second and a half. */
|
|
623
|
+
try {
|
|
624
|
+
page.announceHold()
|
|
625
|
+
} catch {
|
|
626
|
+
/* the stream is gone; the next frame carries the same answer */
|
|
627
|
+
}
|
|
628
|
+
}
|
|
513
629
|
json(200, cursor === undefined ? { ok: true } : { ok: true, cursor })
|
|
514
630
|
} catch (error) {
|
|
515
631
|
json(500, { ok: false, text: String(error?.message ?? error) })
|
|
@@ -668,11 +784,15 @@ export function apply(ctx) {
|
|
|
668
784
|
name: 'browser-in-this-chat',
|
|
669
785
|
order: 858,
|
|
670
786
|
text: [
|
|
671
|
-
'##
|
|
787
|
+
'## 网页任务:人明确点名才开右栏浏览器',
|
|
788
|
+
'右栏「后台浏览器」是一台独立的后台 Chrome,模型能点、能读、能执行脚本;画面只出现在本会话右栏,不占别的会话。它只做「必须动手」的活,默认不开。',
|
|
672
789
|
'',
|
|
673
|
-
'
|
|
674
|
-
'
|
|
675
|
-
'
|
|
790
|
+
'只有人明确说要看网页/用浏览器时才开始:「用网页端 / 在浏览器里 / 打开某个网站 / 上 ChatGPT 生成 / 帮我在这个页面点…」。人只给了链接、没说要用浏览器时,先用 `web_fetch` 或 envoy 读;读不到(登录墙、空壳、要交互)就说明原因再问一句,不要自己开。',
|
|
791
|
+
'不要用它:读公开文章、单个链接正文、文档、搜索结果摘要、GitHub 仓库或 PR 页面——这些能用 `web_fetch` / envoy 读就不用浏览器;一打开就是一个后台 Chrome 进程加一个页签,会话多了很占内存。',
|
|
792
|
+
'人点名之后不问、不另起外部 Chrome;画面留在本会话右栏,做完保留,不主动关。',
|
|
793
|
+
'先接着用已有的页:`browser_view`(拿编号控件与可见文字)→ `browser_click` / `browser_type` → 再 `browser_view` 直到出现要的结果;页面要等就重复读,不 sleep。`browser_open` 只用于没有页、要换新地址、或要明确刷新;同一个地址不要反复重开。',
|
|
794
|
+
'页面很长、只想确认某句话/按钮在哪时用 `browser_find`,别整页读。要读长文(文章、文档、issue、论坛帖)用 `browser_read`:它回 Markdown,只花几十分之一的 token,链接可点;要动手点、填表才用 `browser_view` 拿编号。',
|
|
795
|
+
'要保存页面样子用 `browser_shot`,它会给出 PNG 绝对路径;要交给人看的文件,把绝对路径写进回复。',
|
|
676
796
|
].join('\n'),
|
|
677
797
|
}), 'dsh-inner-browser-use: system prompt section')
|
|
678
798
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-inner-browser-use",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.95",
|
|
4
4
|
"description": "An agent-owned Chromium sidecar mirrored into the DeepSeek Harness right column: the agent drives it, the person watches and can take the mouse, and logins survive restarts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"cordis.patch.yml",
|
|
23
23
|
"icon.svg",
|
|
24
24
|
"scripts/doctor.mjs",
|
|
25
|
+
"scripts/check-flash-window.mjs",
|
|
26
|
+
"scripts/check-flash-mirror.mjs",
|
|
27
|
+
"scripts/check-flash-local.mjs",
|
|
28
|
+
"scripts/check-flash-tools.mjs",
|
|
29
|
+
"runtime/",
|
|
25
30
|
"docs/",
|
|
26
31
|
"README.md",
|
|
27
32
|
"README.zh.md",
|
|
@@ -43,12 +48,21 @@
|
|
|
43
48
|
"dsh": ">=0.2.0"
|
|
44
49
|
},
|
|
45
50
|
"scripts": {
|
|
46
|
-
"test": "node --test test
|
|
51
|
+
"test": "node --test test/artifacts.test.mjs test/client.test.mjs test/cookies.test.mjs test/diff.test.mjs test/fresh.test.mjs test/host.test.mjs test/input.test.mjs test/isolation.test.mjs test/keyboard.test.mjs test/leak.test.mjs test/login.test.mjs test/no-duplicate.test.mjs test/package.test.mjs test/readfault.test.mjs test/refs.test.mjs test/review.test.mjs test/session.test.mjs test/settle.test.mjs test/sidecar.test.mjs test/swf.test.mjs test/tabs.test.mjs test/trouble.test.mjs",
|
|
47
52
|
"doctor": "node scripts/doctor.mjs",
|
|
48
53
|
"breadth": "node scripts/breadth.mjs",
|
|
49
54
|
"live": "node scripts/live.mjs",
|
|
50
55
|
"adopt": "node scripts/check-adopt.mjs",
|
|
51
|
-
"game": "node scripts/check-game.mjs"
|
|
56
|
+
"game": "node scripts/check-game.mjs",
|
|
57
|
+
"modes": "node scripts/check-modes.mjs",
|
|
58
|
+
"ready": "node scripts/check-prepare.mjs",
|
|
59
|
+
"pdf": "node scripts/check-pdf.mjs",
|
|
60
|
+
"swf": "node scripts/swf-inspect.mjs",
|
|
61
|
+
"flash-hidden": "node scripts/check-flash-window.mjs",
|
|
62
|
+
"flash-mirror": "node scripts/check-flash-mirror.mjs",
|
|
63
|
+
"flash-tools": "node scripts/check-flash-tools.mjs",
|
|
64
|
+
"flash-local": "node scripts/check-flash-local.mjs",
|
|
65
|
+
"keys": "node scripts/keyboard.mjs"
|
|
52
66
|
},
|
|
53
67
|
"license": "MIT",
|
|
54
68
|
"author": {
|