Add Desktop CDP helpers for local plugin enablement.

Provide a minimal remote-debugging client and a one-shot enable script for linked Desktop plugins.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-09 22:46:38 +08:00
parent 838ff2fed4
commit 217e1f781a
2 changed files with 392 additions and 0 deletions

View file

@ -0,0 +1,86 @@
/**
* Enable local linked plugins via Desktop CDP (one plugin per eval).
* Usage: node _enable-local-plugins.mjs
*/
import { spawn } from 'node:child_process'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const cdp = path.join(__dirname, '_desktop-cdp.mjs')
const NAMES = [
'dsh-search-mcp',
'dsh-trilium',
'uds-auth',
'dsh-ops-cron',
'dsh-netxops',
'dsh-im-ops',
'dsh-mind-map',
]
function runEval(code, timeoutMs = 90_000) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [cdp, 'eval', code], {
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
})
let out = ''
let err = ''
const t = setTimeout(() => {
child.kill()
reject(new Error(`timeout after ${timeoutMs}ms: ${err || out}`))
}, timeoutMs)
child.stdout.on('data', (d) => { out += d })
child.stderr.on('data', (d) => { err += d })
child.on('close', (code) => {
clearTimeout(t)
if (code !== 0) reject(new Error(err || out || `exit ${code}`))
else {
try { resolve(JSON.parse(out)) } catch { resolve(out) }
}
})
})
}
function enableOne(name) {
// Keep under CDP 60s eval timeout
return runEval(`(async () => {
const sleep = ms => new Promise(r => setTimeout(r, ms))
const byAria = a => [...document.querySelectorAll('button')].find(e => (e.getAttribute('aria-label')||'') === a)
const clickText = t => {
const b = [...document.querySelectorAll('button')].find(e => (e.innerText||'').trim() === t)
if (b) { b.click(); return true }
return false
}
byAria('插件')?.click(); await sleep(800)
const steps = []
const en = byAria('启用 ' + ${JSON.stringify(name)})
if (en) { en.click(); steps.push('启用'); await sleep(2500) }
byAria('查看 ' + ${JSON.stringify(name)})?.click(); await sleep(900)
if (clickText('立即启用')) { steps.push('立即启用'); await sleep(2800) }
byAria('返回插件列表')?.click(); await sleep(350)
byAria('查看 ' + ${JSON.stringify(name)})?.click(); await sleep(700)
const t = document.body.innerText || ''
const idx = t.indexOf(${JSON.stringify(name)})
const bit = idx >= 0 ? t.slice(idx, idx + 280) : ''
const running = /运行中/.test(bit)
const enableStill = !!byAria('启用 ' + ${JSON.stringify(name)})
byAria('返回插件列表')?.click()
return { name: ${JSON.stringify(name)}, steps, running, enableStill, bit: bit.slice(0, 200) }
})()`, 70_000)
}
const results = []
for (const name of NAMES) {
try {
const r = await enableOne(name)
results.push(r)
console.log(JSON.stringify(r))
} catch (e) {
results.push({ name, error: String(e.message || e).slice(0, 300) })
console.error(name, e.message || e)
}
}
const cron = await runEval(`(() => ({ hasCron: /定时任务/.test(document.body.innerText||''), head: (document.body.innerText||'').slice(0,120) }))()`)
console.log(JSON.stringify({ results, cron }, null, 2))