@justlovepl/desktop-bridge 0.0.0-stage → 0.2.6
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/LICENSE +21 -0
- package/README.md +46 -3
- package/THIRD_PARTY_NOTICES.md +33 -0
- package/bin/desktop-bridge.mjs +114 -0
- package/bin/update.mjs +92 -0
- package/dist/client/Install.cmd +3 -0
- package/dist/client/Install.command +3 -0
- package/dist/client/Install.sh +137 -0
- package/dist/client/Pair-again.cmd +3 -0
- package/dist/client/RePair.cmd +9 -0
- package/dist/client/Start.cmd +4 -0
- package/dist/client/Start.command +3 -0
- package/dist/client/Stop.cmd +4 -0
- package/dist/client/Stop.command +3 -0
- package/dist/client/Uninstall.cmd +3 -0
- package/dist/client/Uninstall.command +3 -0
- package/dist/client/Uninstall.sh +3 -0
- package/dist/client/admin-elevate.mjs +48 -0
- package/dist/client/admin-runtime.mjs +119 -0
- package/dist/client/admin-service.mjs +152 -0
- package/dist/client/agent/agent.mjs +264 -0
- package/dist/client/agent/pair.mjs +31 -0
- package/dist/client/connection.json +1 -0
- package/dist/client/control.mjs +75 -0
- package/dist/client/control.ps1 +12 -0
- package/dist/client/environment-probe.mjs +5 -0
- package/dist/client/environment.mjs +99 -0
- package/dist/client/install-options.mjs +43 -0
- package/dist/client/install.mjs +84 -0
- package/dist/client/install.ps1 +108 -0
- package/dist/client/launch.sh +17 -0
- package/dist/client/lifecycle.mjs +42 -0
- package/dist/client/node-runtime.json +30 -0
- package/dist/client/package-lock.json +8457 -0
- package/dist/client/package.json +23 -0
- package/dist/client/runner.mjs +38 -0
- package/dist/client/start.sh +3 -0
- package/dist/client/startup.mjs +60 -0
- package/dist/client/stop.sh +3 -0
- package/dist/client/uninstall.ps1 +6 -0
- package/package.json +28 -6
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "desktop-bridge-client-runtime",
|
|
3
|
+
"version": "0.2.6",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
11
|
+
"@wonderwhy-er/desktop-commander": "0.3.0",
|
|
12
|
+
"ws": "8.22.0"
|
|
13
|
+
},
|
|
14
|
+
"overrides": {
|
|
15
|
+
"md-to-pdf": {
|
|
16
|
+
"chokidar": "^4.0.3"
|
|
17
|
+
},
|
|
18
|
+
"exceljs": {
|
|
19
|
+
"uuid": "^11.1.1"
|
|
20
|
+
},
|
|
21
|
+
"argparse": "^2.0.1"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {fork,spawn} from 'node:child_process';
|
|
4
|
+
import {acquireLock,releaseLock} from './lifecycle.mjs';
|
|
5
|
+
if(!process.argv[2])throw new Error('Usage: node runner.mjs client-data-directory');
|
|
6
|
+
const root=path.resolve(process.argv[2]),flag=path.join(root,'maintenance.flag'),lock=path.join(root,'runner-lock');
|
|
7
|
+
if(fs.existsSync(flag)||!acquireLock(lock))process.exit(0);
|
|
8
|
+
process.on('exit',()=>releaseLock(lock));
|
|
9
|
+
const launch=JSON.parse(fs.readFileSync(path.join(root,'launch.json'),'utf8').replace(/^\uFEFF/,''));
|
|
10
|
+
let child,stopping=false,restartTimer,forceTimer,childStarted=0,childStopping=false;
|
|
11
|
+
function killTree(processChild){
|
|
12
|
+
if(process.platform==='win32'){const killer=spawn('taskkill.exe',['/PID',String(processChild.pid),'/T','/F'],{stdio:'ignore'});killer.on('error',()=>processChild.kill('SIGKILL'));}
|
|
13
|
+
else try{process.kill(-processChild.pid,'SIGKILL');}catch{processChild.kill('SIGKILL');}
|
|
14
|
+
}
|
|
15
|
+
function stopChild(){
|
|
16
|
+
if(!child||childStopping)return;childStopping=true;
|
|
17
|
+
if(child.connected)try{child.send({type:'stop'},()=>{});}catch{}
|
|
18
|
+
forceTimer=setTimeout(()=>{if(child)killTree(child);},10000);
|
|
19
|
+
}
|
|
20
|
+
function stop(){if(stopping)return;stopping=true;clearTimeout(restartTimer);if(child)stopChild();else process.exit(0);}
|
|
21
|
+
process.on('message',message=>{if(message?.type==='stop')stop();});
|
|
22
|
+
if(process.connected)process.on('disconnect',stop);
|
|
23
|
+
for(const signal of ['SIGTERM','SIGINT','SIGHUP'])process.on(signal,stop);
|
|
24
|
+
const watch=setInterval(()=>{
|
|
25
|
+
if(fs.existsSync(flag))return stop();
|
|
26
|
+
if(!child||childStopping)return;
|
|
27
|
+
let timestamp=childStarted;
|
|
28
|
+
try{const heartbeat=JSON.parse(fs.readFileSync(path.join(root,'heartbeat.json'),'utf8'));if(heartbeat.pid===child.pid&&Number.isFinite(heartbeat.time))timestamp=heartbeat.time;}catch{}
|
|
29
|
+
if(Date.now()-timestamp>90000)stopChild();
|
|
30
|
+
},1000);watch.unref();
|
|
31
|
+
function start(){
|
|
32
|
+
if(stopping||fs.existsSync(flag))return stop();
|
|
33
|
+
childStarted=Date.now();childStopping=false;
|
|
34
|
+
child=fork(launch.agent,[launch.config],{execPath:process.execPath,cwd:root,detached:process.platform!=='win32',stdio:['ignore','ignore','ignore','ipc']});
|
|
35
|
+
child.on('error',()=>{});
|
|
36
|
+
child.once('exit',()=>{clearTimeout(forceTimer);if(process.platform!=='win32')try{process.kill(-child.pid,'SIGKILL');}catch{}child=null;if(stopping)process.exit(0);restartTimer=setTimeout(start,10000);});
|
|
37
|
+
}
|
|
38
|
+
start();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {spawn} from 'node:child_process';
|
|
4
|
+
import {run,startupFile,removeStartup,stopClient,livePid} from './lifecycle.mjs';
|
|
5
|
+
export const shellQuote=value=>"'"+String(value).replace(/'/g,"'\\''")+"'";
|
|
6
|
+
export const powershellQuote=value=>"'"+String(value).replace(/'/g,"''")+"'";
|
|
7
|
+
const xml=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
8
|
+
const desktopQuote=s=>'"'+String(s).replace(/(["\\`$])/g,'\\$1').replace(/%/g,'%%')+'"';
|
|
9
|
+
export function commandFor(node,root,action,platform=process.platform) {
|
|
10
|
+
const argv=[node,path.join(root,'control.mjs'),...action.split(' ')];
|
|
11
|
+
return platform==='win32'?'& '+argv.map(powershellQuote).join(' '):argv.map(shellQuote).join(' ');
|
|
12
|
+
}
|
|
13
|
+
export function startupContent(node,root,platform=process.platform) {
|
|
14
|
+
if(platform==='darwin')return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>de.desktopbridge.agent</string><key>ProgramArguments</key><array>${[node,path.join(root,'runner.mjs'),root].map(s=>'<string>'+xml(s)+'</string>').join('')}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>WorkingDirectory</key><string>${xml(root)}</string></dict></plist>`;
|
|
15
|
+
if(platform==='linux')return `[Desktop Entry]\nType=Application\nName=Desktop Bridge\nExec=${[node,path.join(root,'runner.mjs'),root].map(desktopQuote).join(' ')}\nTerminal=false\nX-GNOME-Autostart-enabled=true\n`;
|
|
16
|
+
throw new Error('Use the Windows startup shortcut helper.');
|
|
17
|
+
}
|
|
18
|
+
export async function registerStartup(node,root) {
|
|
19
|
+
const file=startupFile(root);fs.mkdirSync(path.dirname(file),{recursive:true});
|
|
20
|
+
if(process.platform==='win32'){
|
|
21
|
+
const ps=powershellQuote;
|
|
22
|
+
// Target Node directly. Routing through cmd.exe would expand % and ! in paths.
|
|
23
|
+
const quote=value=>'"'+value.replace(/(\\*)"/g,'$1$1\\"').replace(/(\\+)$/,'$1$1')+'"';
|
|
24
|
+
const argv=[path.join(root,'runner.mjs'),root].map(quote).join(' ');
|
|
25
|
+
await run('powershell.exe',['-NoProfile','-NonInteractive','-Command',`$s=(New-Object -ComObject WScript.Shell).CreateShortcut(${ps(file)});$s.TargetPath=${ps(node)};$s.Arguments=${ps(argv)};$s.WorkingDirectory=${ps(root)};$s.WindowStyle=7;$s.Save()`]);
|
|
26
|
+
}else{
|
|
27
|
+
fs.writeFileSync(file,startupContent(node,root));
|
|
28
|
+
if(process.platform==='darwin'){
|
|
29
|
+
try{await run('launchctl',['bootout','gui/'+process.getuid(),file],{stdio:'ignore'});}catch{}
|
|
30
|
+
try{await run('launchctl',['bootstrap','gui/'+process.getuid(),file],{stdio:'ignore'});}catch{console.log('登录自启设置已保存,下次登录图形界面时生效。');}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function writeInstructions(root,node,mode,privilege='standard') {
|
|
35
|
+
const contents=`Desktop Bridge 0.2.6 使用说明\n\n权限:${privilege==='administrator'?'管理员(操作将由系统提示授权;后台使用受保护系统服务)':'当前用户'}\n当前模式:${mode==='manual'?'手动 manual':'后台 background'}\n${mode==='manual'?'使用时运行启动命令,保持窗口,按 Ctrl+C 或关闭窗口结束。客户端与本地工具进程会一起停止。未设置登录自启。':'客户端现在后台运行,登录系统后自动启动。这是当前用户登录后自启,不是登录前的系统服务。'}\n\n${process.platform==='win32'?'请将以下命令复制到 PowerShell。':'请将以下命令复制到终端。'}\n\n启动:\n${commandFor(node,root,'start')}\n\n停止:\n${commandFor(node,root,'stop')}\n\n状态:\n${commandFor(node,root,'status')}\n\n切换为手动模式(停止后台实例,移除登录自启):\n${commandFor(node,root,'mode manual')}\n\n切换为后台模式(现在启动,登录系统后自动启动):\n${commandFor(node,root,'mode background')}\n\n本目录中的快捷入口:${process.platform==='win32'?'Start.cmd / Stop.cmd':process.platform==='darwin'?'Start.command / Stop.command':'start.sh / stop.sh'}。\n启动、停止和切换模式使用已安装的私有环境,不会重新安装依赖。\n\n重新配对(旧 Token 失效时):\n${commandFor(node,root,'pair')}\n${process.platform==='win32'?'或双击 RePair.cmd。':'在终端执行上述命令。'}\n配对必须由用户在网页重新确认。\n如果出现 authStatus=re_pair_required,请先重新配对。\n\n请保留本目录,其中保存设备凭据、日志与可恢复的任务结果。停止不会删除这些数据。\n`;
|
|
36
|
+
fs.writeFileSync(path.join(root,'START-HERE.txt'),contents+(privilege==='administrator'?'\n管理员模式下,普通用户不能修改后台程序文件。系统隐私、Google 登录与 ChatGPT 工具确认仍需分别授权。\n':''),{mode:0o600});
|
|
37
|
+
}
|
|
38
|
+
export async function startBackground(root,node) {
|
|
39
|
+
let auth={};try{auth=JSON.parse(fs.readFileSync(path.join(root,'auth-status.json'),'utf8'));}catch{}
|
|
40
|
+
if(auth.authStatus==='re_pair_required')throw new Error('设备需要重新配对。请先执行 RePair.cmd 或 control.mjs pair。');
|
|
41
|
+
if(livePid(path.join(root,'runner-lock','pid'))){console.log('Client is already running.');return;}
|
|
42
|
+
fs.rmSync(path.join(root,'maintenance.flag'),{force:true});
|
|
43
|
+
const child=spawn(node,[path.join(root,'runner.mjs'),root],{detached:true,stdio:'ignore'});
|
|
44
|
+
await new Promise((resolve,reject)=>{child.once('spawn',resolve);child.once('error',reject);});child.unref();
|
|
45
|
+
}
|
|
46
|
+
export async function setMode(root,node,mode) {
|
|
47
|
+
if(!['manual','background'].includes(mode))throw new Error('Mode must be manual or background');
|
|
48
|
+
await stopClient(root);
|
|
49
|
+
await removeStartup(root);
|
|
50
|
+
const settings={mode,version:'0.2.6'};
|
|
51
|
+
fs.writeFileSync(path.join(root,'client-settings.json.tmp'),JSON.stringify(settings,null,2),{mode:0o600});
|
|
52
|
+
fs.renameSync(path.join(root,'client-settings.json.tmp'),path.join(root,'client-settings.json'));
|
|
53
|
+
writeInstructions(root,node,mode);
|
|
54
|
+
if(mode==='background'){
|
|
55
|
+
await registerStartup(node,root);
|
|
56
|
+
let auth={};try{auth=JSON.parse(fs.readFileSync(path.join(root,'auth-status.json'),'utf8'));}catch{}
|
|
57
|
+
if(auth.authStatus!=='re_pair_required')await startBackground(root,node);
|
|
58
|
+
else console.log('启动模式已保存,但授权已撤销。请先运行 RePair.cmd 重新配对。');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
param([switch]$Purge,[string]$DataDirectory)
|
|
2
|
+
$ErrorActionPreference = 'Stop'
|
|
3
|
+
$argsForInstaller = @{'Uninstall'=$true}
|
|
4
|
+
if ($Purge) { $argsForInstaller['Purge']=$true }
|
|
5
|
+
if ($DataDirectory) { $argsForInstaller['DataDirectory']=$DataDirectory }
|
|
6
|
+
& (Join-Path $PSScriptRoot 'install.ps1') @argsForInstaller
|
package/package.json
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@justlovepl/desktop-bridge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@justlovepl/desktop-bridge",
|
|
3
|
+
"version": "0.2.6",
|
|
4
|
+
"description": "Cross-platform Desktop Bridge agent installer and lifecycle CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://mcp.ilovestudy.de/start.html",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/bb-boy/desktop-bridge.git",
|
|
11
|
+
"directory": "npm-installer"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"desktop-bridge": "./bin/desktop-bridge.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": ["bin/", "dist/", "README.md", "THIRD_PARTY_NOTICES.md"],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=24 <25"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"prepack": "node scripts/stage-client.mjs",
|
|
22
|
+
"test": "node --test test/*.test.mjs"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"keywords": ["desktop-bridge", "mcp", "remote-desktop", "agent", "command-line"]
|
|
28
|
+
}
|