@hcu-lab.me/mcp-device 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/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +22 -0
- package/dist/command-manager.d.ts +8 -0
- package/dist/command-manager.js +241 -0
- package/dist/config-field-definitions.d.ts +41 -0
- package/dist/config-field-definitions.js +37 -0
- package/dist/config-manager.d.ts +102 -0
- package/dist/config-manager.js +291 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +10 -0
- package/dist/custom-stdio.d.ts +51 -0
- package/dist/custom-stdio.js +358 -0
- package/dist/data/official-app-ca.pem +12 -0
- package/dist/data/onboarding-prompts.json +70 -0
- package/dist/device/device-state.d.ts +13 -0
- package/dist/device/device-state.js +100 -0
- package/dist/device/device-status.d.ts +81 -0
- package/dist/device/device-status.js +156 -0
- package/dist/device/device.d.ts +16 -0
- package/dist/device/device.js +190 -0
- package/dist/device/execution-engine.d.ts +144 -0
- package/dist/device/execution-engine.js +195 -0
- package/dist/device/gateway-channel.d.ts +48 -0
- package/dist/device/gateway-channel.js +384 -0
- package/dist/device/gateway-config.d.ts +40 -0
- package/dist/device/gateway-config.js +207 -0
- package/dist/device/gateway-identity.d.ts +35 -0
- package/dist/device/gateway-identity.js +178 -0
- package/dist/device/gateway-pairing.d.ts +25 -0
- package/dist/device/gateway-pairing.js +105 -0
- package/dist/device/gateway-secure-transport.d.ts +25 -0
- package/dist/device/gateway-secure-transport.js +117 -0
- package/dist/device/gateway-tool-adapter.d.ts +17 -0
- package/dist/device/gateway-tool-adapter.js +284 -0
- package/dist/device/gateway-url-policy.d.ts +3 -0
- package/dist/device/gateway-url-policy.js +43 -0
- package/dist/device/linux-service.d.ts +70 -0
- package/dist/device/linux-service.js +268 -0
- package/dist/device/official-trust.d.ts +7 -0
- package/dist/device/official-trust.js +49 -0
- package/dist/device/project-inspection.d.ts +1 -0
- package/dist/device/project-inspection.js +180 -0
- package/dist/device/runtime-owner.d.ts +41 -0
- package/dist/device/runtime-owner.js +177 -0
- package/dist/device/windows-dpapi.d.ts +11 -0
- package/dist/device/windows-dpapi.js +67 -0
- package/dist/device/windows-service.d.ts +56 -0
- package/dist/device/windows-service.js +238 -0
- package/dist/error-handlers.d.ts +7 -0
- package/dist/error-handlers.js +15 -0
- package/dist/handlers/edit-search-handlers.d.ts +6 -0
- package/dist/handlers/edit-search-handlers.js +6 -0
- package/dist/handlers/filesystem-handlers.d.ts +38 -0
- package/dist/handlers/filesystem-handlers.js +440 -0
- package/dist/handlers/history-handlers.d.ts +12 -0
- package/dist/handlers/history-handlers.js +67 -0
- package/dist/handlers/index.d.ts +6 -0
- package/dist/handlers/index.js +7 -0
- package/dist/handlers/process-handlers.d.ts +9 -0
- package/dist/handlers/process-handlers.js +15 -0
- package/dist/handlers/search-handlers.d.ts +17 -0
- package/dist/handlers/search-handlers.js +224 -0
- package/dist/handlers/terminal-handlers.d.ts +21 -0
- package/dist/handlers/terminal-handlers.js +35 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +138 -0
- package/dist/mcp-device.d.ts +2 -0
- package/dist/mcp-device.js +7 -0
- package/dist/npm-scripts/remote.d.ts +3 -0
- package/dist/npm-scripts/remote.js +314 -0
- package/dist/search-manager.d.ts +147 -0
- package/dist/search-manager.js +828 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.js +1563 -0
- package/dist/terminal-manager.d.ts +104 -0
- package/dist/terminal-manager.js +635 -0
- package/dist/tools/config.d.ts +96 -0
- package/dist/tools/config.js +260 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +435 -0
- package/dist/tools/feedback.d.ts +8 -0
- package/dist/tools/feedback.js +132 -0
- package/dist/tools/filesystem.d.ts +79 -0
- package/dist/tools/filesystem.js +947 -0
- package/dist/tools/fuzzySearch.d.ts +12 -0
- package/dist/tools/fuzzySearch.js +74 -0
- package/dist/tools/fuzzySearchCore.d.ts +52 -0
- package/dist/tools/fuzzySearchCore.js +125 -0
- package/dist/tools/improved-process-tools.d.ts +24 -0
- package/dist/tools/improved-process-tools.js +627 -0
- package/dist/tools/mime-types.d.ts +3 -0
- package/dist/tools/mime-types.js +28 -0
- package/dist/tools/pdf/extract-images.d.ts +34 -0
- package/dist/tools/pdf/extract-images.js +132 -0
- package/dist/tools/pdf/index.d.ts +6 -0
- package/dist/tools/pdf/index.js +3 -0
- package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
- package/dist/tools/pdf/lib/pdf2md.js +76 -0
- package/dist/tools/pdf/manipulations.d.ts +13 -0
- package/dist/tools/pdf/manipulations.js +99 -0
- package/dist/tools/pdf/markdown.d.ts +26 -0
- package/dist/tools/pdf/markdown.js +275 -0
- package/dist/tools/pdf/utils.d.ts +12 -0
- package/dist/tools/pdf/utils.js +34 -0
- package/dist/tools/process.d.ts +3 -0
- package/dist/tools/process.js +56 -0
- package/dist/tools/prompts.d.ts +28 -0
- package/dist/tools/prompts.js +259 -0
- package/dist/tools/schemas.d.ts +454 -0
- package/dist/tools/schemas.js +242 -0
- package/dist/tools/usage.d.ts +5 -0
- package/dist/tools/usage.js +24 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.js +1 -0
- package/dist/ui/config-editor/config-editor-runtime.js +150 -0
- package/dist/ui/config-editor/index.html +13 -0
- package/dist/ui/config-editor/src/app.d.ts +43 -0
- package/dist/ui/config-editor/src/app.js +831 -0
- package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
- package/dist/ui/config-editor/src/array-modal.js +185 -0
- package/dist/ui/config-editor/src/main.d.ts +1 -0
- package/dist/ui/config-editor/src/main.js +2 -0
- package/dist/ui/config-editor/styles.css +587 -0
- package/dist/ui/contracts.d.ts +14 -0
- package/dist/ui/contracts.js +21 -0
- package/dist/ui/file-preview/index.html +16 -0
- package/dist/ui/file-preview/preview-runtime.js +469 -0
- package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
- package/dist/ui/file-preview/shared/preview-file-types.js +59 -0
- package/dist/ui/file-preview/src/app.d.ts +4 -0
- package/dist/ui/file-preview/src/app.js +680 -0
- package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
- package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
- package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
- package/dist/ui/file-preview/src/components/highlighting.js +54 -0
- package/dist/ui/file-preview/src/components/html-renderer.d.ts +5 -0
- package/dist/ui/file-preview/src/components/html-renderer.js +47 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.js +59 -0
- package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
- package/dist/ui/file-preview/src/directory-controller.js +240 -0
- package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
- package/dist/ui/file-preview/src/document-layout.js +109 -0
- package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
- package/dist/ui/file-preview/src/document-outline.js +97 -0
- package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
- package/dist/ui/file-preview/src/document-workspace.js +33 -0
- package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
- package/dist/ui/file-preview/src/file-type-handlers.js +109 -0
- package/dist/ui/file-preview/src/host/external-actions.d.ts +8 -0
- package/dist/ui/file-preview/src/host/external-actions.js +55 -0
- package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
- package/dist/ui/file-preview/src/host/selection-context.js +106 -0
- package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
- package/dist/ui/file-preview/src/image-preview.js +21 -0
- package/dist/ui/file-preview/src/main.d.ts +1 -0
- package/dist/ui/file-preview/src/main.js +5 -0
- package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
- package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
- package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
- package/dist/ui/file-preview/src/markdown/controller.js +1043 -0
- package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
- package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
- package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
- package/dist/ui/file-preview/src/markdown/linking.js +228 -0
- package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
- package/dist/ui/file-preview/src/markdown/outline.js +16 -0
- package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
- package/dist/ui/file-preview/src/markdown/parser.js +38 -0
- package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
- package/dist/ui/file-preview/src/markdown/preview.js +20 -0
- package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
- package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
- package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
- package/dist/ui/file-preview/src/markdown/utils.js +15 -0
- package/dist/ui/file-preview/src/model.d.ts +35 -0
- package/dist/ui/file-preview/src/model.js +1 -0
- package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
- package/dist/ui/file-preview/src/panel-actions.js +182 -0
- package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
- package/dist/ui/file-preview/src/path-utils.js +64 -0
- package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
- package/dist/ui/file-preview/src/payload-utils.js +119 -0
- package/dist/ui/file-preview/src/types.d.ts +1 -0
- package/dist/ui/file-preview/src/types.js +1 -0
- package/dist/ui/file-preview/styles.css +1631 -0
- package/dist/ui/resources.d.ts +28 -0
- package/dist/ui/resources.js +86 -0
- package/dist/ui/shared/compact-row.d.ts +11 -0
- package/dist/ui/shared/compact-row.js +18 -0
- package/dist/ui/shared/escape-html.d.ts +4 -0
- package/dist/ui/shared/escape-html.js +11 -0
- package/dist/ui/shared/host-context.d.ts +15 -0
- package/dist/ui/shared/host-context.js +51 -0
- package/dist/ui/shared/tool-bridge.d.ts +30 -0
- package/dist/ui/shared/tool-bridge.js +137 -0
- package/dist/ui/shared/tool-shell.d.ts +25 -0
- package/dist/ui/shared/tool-shell.js +107 -0
- package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
- package/dist/ui/shared/ui-event-tracker.js +27 -0
- package/dist/ui/shared/widget-state.d.ts +33 -0
- package/dist/ui/shared/widget-state.js +158 -0
- package/dist/utils/ab-test.d.ts +12 -0
- package/dist/utils/ab-test.js +104 -0
- package/dist/utils/capture.d.ts +35 -0
- package/dist/utils/capture.js +481 -0
- package/dist/utils/dockerPrompt.d.ts +17 -0
- package/dist/utils/dockerPrompt.js +53 -0
- package/dist/utils/feature-flags.d.ts +59 -0
- package/dist/utils/feature-flags.js +215 -0
- package/dist/utils/files/base.d.ts +173 -0
- package/dist/utils/files/base.js +5 -0
- package/dist/utils/files/binary.d.ts +21 -0
- package/dist/utils/files/binary.js +65 -0
- package/dist/utils/files/docx.d.ts +47 -0
- package/dist/utils/files/docx.js +679 -0
- package/dist/utils/files/excel.d.ts +24 -0
- package/dist/utils/files/excel.js +437 -0
- package/dist/utils/files/factory.d.ts +41 -0
- package/dist/utils/files/factory.js +113 -0
- package/dist/utils/files/image.d.ts +21 -0
- package/dist/utils/files/image.js +78 -0
- package/dist/utils/files/index.d.ts +10 -0
- package/dist/utils/files/index.js +13 -0
- package/dist/utils/files/pdf.d.ts +32 -0
- package/dist/utils/files/pdf.js +142 -0
- package/dist/utils/files/text.d.ts +63 -0
- package/dist/utils/files/text.js +370 -0
- package/dist/utils/fuzzySearchLogger.d.ts +30 -0
- package/dist/utils/fuzzySearchLogger.js +126 -0
- package/dist/utils/lineEndingHandler.d.ts +21 -0
- package/dist/utils/lineEndingHandler.js +77 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.js +72 -0
- package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
- package/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/utils/open-browser.d.ts +9 -0
- package/dist/utils/open-browser.js +42 -0
- package/dist/utils/process-detection.d.ts +23 -0
- package/dist/utils/process-detection.js +150 -0
- package/dist/utils/ripgrep-resolver.d.ts +9 -0
- package/dist/utils/ripgrep-resolver.js +74 -0
- package/dist/utils/system-info.d.ts +71 -0
- package/dist/utils/system-info.js +712 -0
- package/dist/utils/toolHistory.d.ts +86 -0
- package/dist/utils/toolHistory.js +257 -0
- package/dist/utils/trackTools.d.ts +6 -0
- package/dist/utils/trackTools.js +54 -0
- package/dist/utils/unsupportedParams.d.ts +24 -0
- package/dist/utils/unsupportedParams.js +66 -0
- package/dist/utils/usageTracker.d.ts +133 -0
- package/dist/utils/usageTracker.js +477 -0
- package/dist/utils/welcome-onboarding.d.ts +11 -0
- package/dist/utils/welcome-onboarding.js +99 -0
- package/dist/utils/withTimeout.d.ts +28 -0
- package/dist/utils/withTimeout.js +85 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +131 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from 'child_process';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import readline from 'readline/promises';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
const execFileAsync = promisify(execFileCallback);
|
|
8
|
+
const SYSTEMD_UNIT = 'mcp-device.service';
|
|
9
|
+
const SYSTEMD_OWNER_MARKER = '# Managed by MCP Device';
|
|
10
|
+
const PM2_NAME = 'mcp-device';
|
|
11
|
+
async function defaultExecFile(file, args) {
|
|
12
|
+
const result = await execFileAsync(file, args, { windowsHide: true, encoding: 'utf8' });
|
|
13
|
+
return { stdout: String(result.stdout || ''), stderr: String(result.stderr || '') };
|
|
14
|
+
}
|
|
15
|
+
function quoteSystemdArg(value) {
|
|
16
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value))
|
|
17
|
+
return value;
|
|
18
|
+
return `"${value.replace(/([\\"])/g, '\\$1')}"`;
|
|
19
|
+
}
|
|
20
|
+
function defaultUnitPath() {
|
|
21
|
+
const configHome = String(process.env.XDG_CONFIG_HOME || '').trim() || path.join(os.homedir(), '.config');
|
|
22
|
+
return path.join(configHome, 'systemd', 'user', SYSTEMD_UNIT);
|
|
23
|
+
}
|
|
24
|
+
function guidance() {
|
|
25
|
+
return 'Run `pm2 startup` for this user and complete the printed setup, then retry; or choose the systemd-user alternative.';
|
|
26
|
+
}
|
|
27
|
+
export async function chooseLinuxManager({ ask } = {}) {
|
|
28
|
+
let answer;
|
|
29
|
+
if (ask)
|
|
30
|
+
answer = await ask();
|
|
31
|
+
else {
|
|
32
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
33
|
+
throw new Error('Linux install requires an interactive manager choice: systemd-user or PM2.');
|
|
34
|
+
}
|
|
35
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
36
|
+
try {
|
|
37
|
+
answer = await rl.question('Background manager [systemd/pm2] (systemd): ');
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
rl.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const value = String(answer || '').trim().toLowerCase();
|
|
44
|
+
if (!value || ['systemd', 'systemd-user', 'systemd_user', '1'].includes(value))
|
|
45
|
+
return 'systemd';
|
|
46
|
+
if (['pm2', '2'].includes(value))
|
|
47
|
+
return 'pm2';
|
|
48
|
+
throw new Error('Choose `systemd` or `pm2`.');
|
|
49
|
+
}
|
|
50
|
+
export class SystemdUserDeviceService {
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
this.platform = options.platform || process.platform;
|
|
53
|
+
this.execFile = options.execFile || defaultExecFile;
|
|
54
|
+
this.unitPath = path.resolve(options.unitPath || defaultUnitPath());
|
|
55
|
+
this.nodePath = options.platform === 'linux' && options.nodePath ? String(options.nodePath) : path.resolve(options.nodePath || process.execPath);
|
|
56
|
+
this.entrypoint = options.platform === 'linux' && options.entrypoint ? String(options.entrypoint) : path.resolve(options.entrypoint || process.argv[1]);
|
|
57
|
+
this.pathValue = String(options.pathValue ?? process.env.PATH ?? '/usr/local/bin:/usr/bin:/bin');
|
|
58
|
+
}
|
|
59
|
+
assertLinux() {
|
|
60
|
+
if (this.platform !== 'linux')
|
|
61
|
+
throw new Error('systemd-user lifecycle is supported on Linux only.');
|
|
62
|
+
}
|
|
63
|
+
async unitExists() {
|
|
64
|
+
try {
|
|
65
|
+
return (await fs.stat(this.unitPath)).isFile();
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error?.code === 'ENOENT')
|
|
69
|
+
return false;
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async assertOwnedUnitIfPresent() {
|
|
74
|
+
if (!await this.unitExists())
|
|
75
|
+
return false;
|
|
76
|
+
const text = await fs.readFile(this.unitPath, 'utf8');
|
|
77
|
+
if (!text.includes(SYSTEMD_OWNER_MARKER)) {
|
|
78
|
+
throw new Error(`systemd ownership conflict: ${this.unitPath} is not owned by MCP Device.`);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
unitText() {
|
|
83
|
+
return [
|
|
84
|
+
SYSTEMD_OWNER_MARKER,
|
|
85
|
+
'[Unit]',
|
|
86
|
+
'Description=MCP Device',
|
|
87
|
+
'After=network-online.target',
|
|
88
|
+
'',
|
|
89
|
+
'[Service]',
|
|
90
|
+
'Type=simple',
|
|
91
|
+
`Environment="PATH=${this.pathValue.replace(/["\\]/g, match => `\\${match}`)}"`,
|
|
92
|
+
`ExecStart=${quoteSystemdArg(this.nodePath)} ${quoteSystemdArg(this.entrypoint)} --service --manager=systemd`,
|
|
93
|
+
'Restart=on-failure',
|
|
94
|
+
'RestartSec=3',
|
|
95
|
+
'',
|
|
96
|
+
'[Install]',
|
|
97
|
+
'WantedBy=default.target',
|
|
98
|
+
''
|
|
99
|
+
].join('\n');
|
|
100
|
+
}
|
|
101
|
+
async install() {
|
|
102
|
+
this.assertLinux();
|
|
103
|
+
await this.assertOwnedUnitIfPresent();
|
|
104
|
+
await this.execFile('systemctl', ['--user', 'show-environment']);
|
|
105
|
+
await fs.mkdir(path.dirname(this.unitPath), { recursive: true });
|
|
106
|
+
await fs.writeFile(this.unitPath, this.unitText(), { mode: 0o600 });
|
|
107
|
+
await this.execFile('systemctl', ['--user', 'daemon-reload']);
|
|
108
|
+
await this.execFile('systemctl', ['--user', 'enable', '--now', SYSTEMD_UNIT]);
|
|
109
|
+
const status = await this.status();
|
|
110
|
+
if (!status.autostart || !status.running)
|
|
111
|
+
throw new Error('systemd-user did not become enabled and active.');
|
|
112
|
+
}
|
|
113
|
+
async stop() {
|
|
114
|
+
this.assertLinux();
|
|
115
|
+
if (await this.assertOwnedUnitIfPresent())
|
|
116
|
+
await this.execFile('systemctl', ['--user', 'stop', SYSTEMD_UNIT]);
|
|
117
|
+
}
|
|
118
|
+
async status() {
|
|
119
|
+
this.assertLinux();
|
|
120
|
+
if (!await this.assertOwnedUnitIfPresent())
|
|
121
|
+
return { installed: false, running: false, autostart: false, manager: 'systemd' };
|
|
122
|
+
let autostart = false;
|
|
123
|
+
let running = false;
|
|
124
|
+
try {
|
|
125
|
+
autostart = /enabled|static/i.test(String((await this.execFile('systemctl', ['--user', 'is-enabled', SYSTEMD_UNIT])).stdout));
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
try {
|
|
129
|
+
running = /^active$/i.test(String((await this.execFile('systemctl', ['--user', 'is-active', SYSTEMD_UNIT])).stdout).trim());
|
|
130
|
+
}
|
|
131
|
+
catch { }
|
|
132
|
+
return { installed: true, running, autostart, manager: 'systemd' };
|
|
133
|
+
}
|
|
134
|
+
async uninstall() {
|
|
135
|
+
this.assertLinux();
|
|
136
|
+
if (await this.assertOwnedUnitIfPresent()) {
|
|
137
|
+
try {
|
|
138
|
+
await this.execFile('systemctl', ['--user', 'disable', '--now', SYSTEMD_UNIT]);
|
|
139
|
+
}
|
|
140
|
+
catch { }
|
|
141
|
+
await fs.rm(this.unitPath, { force: true });
|
|
142
|
+
await this.execFile('systemctl', ['--user', 'daemon-reload']);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export class Pm2DeviceService {
|
|
147
|
+
constructor(options = {}) {
|
|
148
|
+
this.platform = options.platform || process.platform;
|
|
149
|
+
this.execFile = options.execFile || defaultExecFile;
|
|
150
|
+
this.pm2Path = options.pm2Path || 'pm2';
|
|
151
|
+
this.nodePath = options.platform === 'linux' && options.nodePath ? String(options.nodePath) : path.resolve(options.nodePath || process.execPath);
|
|
152
|
+
this.entrypoint = options.platform === 'linux' && options.entrypoint ? String(options.entrypoint) : path.resolve(options.entrypoint || process.argv[1]);
|
|
153
|
+
this.user = String(options.user || process.env.USER || process.env.LOGNAME || os.userInfo().username);
|
|
154
|
+
this.pm2Home = options.platform === 'linux' && options.pm2Home ? String(options.pm2Home) : path.resolve(options.pm2Home || process.env.PM2_HOME || path.join(os.homedir(), '.pm2'));
|
|
155
|
+
}
|
|
156
|
+
assertLinux() {
|
|
157
|
+
if (this.platform !== 'linux')
|
|
158
|
+
throw new Error('PM2 lifecycle is supported on Linux only.');
|
|
159
|
+
}
|
|
160
|
+
async processRow() {
|
|
161
|
+
let rows;
|
|
162
|
+
try {
|
|
163
|
+
rows = JSON.parse(String((await this.execFile(this.pm2Path, ['jlist'])).stdout || '[]'));
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return Array.isArray(rows) ? rows.find(item => item?.name === PM2_NAME) || null : null;
|
|
169
|
+
}
|
|
170
|
+
assertOwnedProcess(row) {
|
|
171
|
+
if (!row)
|
|
172
|
+
return;
|
|
173
|
+
const env = row?.pm2_env || {};
|
|
174
|
+
const execPath = String(env.pm_exec_path || '').replace(/\\/g, '/');
|
|
175
|
+
const interpreter = String(env.exec_interpreter || '').replace(/\\/g, '/');
|
|
176
|
+
const args = Array.isArray(env.args) ? env.args.map((value) => String(value)) : [];
|
|
177
|
+
if (execPath !== this.entrypoint.replace(/\\/g, '/') ||
|
|
178
|
+
interpreter !== this.nodePath.replace(/\\/g, '/') ||
|
|
179
|
+
!args.includes('--service') || !args.includes('--manager=pm2')) {
|
|
180
|
+
throw new Error('PM2 ownership conflict: process named mcp-device is not owned by this MCP Device installation.');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async proveStartup() {
|
|
184
|
+
this.assertLinux();
|
|
185
|
+
try {
|
|
186
|
+
await this.execFile(this.pm2Path, ['--version']);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
throw new Error(`PM2 is not available for the current user. ${guidance()}`);
|
|
190
|
+
}
|
|
191
|
+
const unit = `pm2-${this.user}.service`;
|
|
192
|
+
let evidence = '';
|
|
193
|
+
try {
|
|
194
|
+
evidence = String((await this.execFile('systemctl', ['show', unit, '--property=FragmentPath,ExecStart,Environment', '--no-pager'])).stdout || '');
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
try {
|
|
198
|
+
evidence = String((await this.execFile('systemctl', ['--user', 'show', unit, '--property=FragmentPath,ExecStart,Environment', '--no-pager'])).stdout || '');
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
throw new Error(`PM2 startup integration is not proven. ${guidance()}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const normalized = evidence.replace(/\\/g, '/');
|
|
205
|
+
const home = this.pm2Home.replace(/\\/g, '/');
|
|
206
|
+
if (!/pm2/i.test(normalized) || !/resurrect/i.test(normalized) || !normalized.includes(home)) {
|
|
207
|
+
throw new Error(`PM2 startup integration does not match this user/PM2_HOME. ${guidance()}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async install() {
|
|
211
|
+
await this.proveStartup();
|
|
212
|
+
const existing = await this.processRow();
|
|
213
|
+
this.assertOwnedProcess(existing);
|
|
214
|
+
if (!existing) {
|
|
215
|
+
await this.execFile(this.pm2Path, ['start', this.entrypoint, '--name', PM2_NAME, '--interpreter', this.nodePath, '--', '--service', '--manager=pm2']);
|
|
216
|
+
}
|
|
217
|
+
else if (String(existing?.pm2_env?.status || '').toLowerCase() !== 'online') {
|
|
218
|
+
await this.execFile(this.pm2Path, ['restart', PM2_NAME]);
|
|
219
|
+
}
|
|
220
|
+
await this.execFile(this.pm2Path, ['save']);
|
|
221
|
+
const status = await this.status();
|
|
222
|
+
if (!status.running)
|
|
223
|
+
throw new Error('PM2 process did not become online.');
|
|
224
|
+
}
|
|
225
|
+
async stop() {
|
|
226
|
+
this.assertLinux();
|
|
227
|
+
const row = await this.processRow();
|
|
228
|
+
this.assertOwnedProcess(row);
|
|
229
|
+
if (row)
|
|
230
|
+
await this.execFile(this.pm2Path, ['stop', PM2_NAME]);
|
|
231
|
+
}
|
|
232
|
+
async status() {
|
|
233
|
+
this.assertLinux();
|
|
234
|
+
const row = await this.processRow();
|
|
235
|
+
if (!row)
|
|
236
|
+
return { installed: false, running: false, autostart: true, manager: 'pm2' };
|
|
237
|
+
this.assertOwnedProcess(row);
|
|
238
|
+
return {
|
|
239
|
+
installed: true,
|
|
240
|
+
running: String(row?.pm2_env?.status || '').toLowerCase() === 'online',
|
|
241
|
+
autostart: true,
|
|
242
|
+
manager: 'pm2'
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async uninstall() {
|
|
246
|
+
this.assertLinux();
|
|
247
|
+
const row = await this.processRow();
|
|
248
|
+
this.assertOwnedProcess(row);
|
|
249
|
+
if (!row)
|
|
250
|
+
return;
|
|
251
|
+
await this.execFile(this.pm2Path, ['delete', PM2_NAME]);
|
|
252
|
+
await this.execFile(this.pm2Path, ['save']);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export async function discoverLinuxManagers(options = {}) {
|
|
256
|
+
if (process.platform !== 'linux')
|
|
257
|
+
return [];
|
|
258
|
+
const systemd = options.systemd || new SystemdUserDeviceService();
|
|
259
|
+
const pm2 = options.pm2 || new Pm2DeviceService();
|
|
260
|
+
const results = [];
|
|
261
|
+
const systemdStatus = await systemd.status().catch(() => ({ installed: false, running: false, autostart: false, manager: 'systemd' }));
|
|
262
|
+
const pm2Status = await pm2.status().catch(() => ({ installed: false, running: false, autostart: false, manager: 'pm2' }));
|
|
263
|
+
if (systemdStatus.installed)
|
|
264
|
+
results.push(systemdStatus);
|
|
265
|
+
if (pm2Status.installed)
|
|
266
|
+
results.push(pm2Status);
|
|
267
|
+
return results;
|
|
268
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GatewayDeviceConfigStore, type GatewayDeviceConfig } from './gateway-config.js';
|
|
2
|
+
export declare const OFFICIAL_GATEWAY_URL = "https://mcp-v2.hcu-lab.me/mcp";
|
|
3
|
+
export declare function isOfficialGatewayUrl(value: string | null | undefined): boolean;
|
|
4
|
+
export declare function bootstrapOfficialGatewayTrust(store: GatewayDeviceConfigStore, options?: {
|
|
5
|
+
gatewayUrl?: string | null;
|
|
6
|
+
caPath?: string;
|
|
7
|
+
}): Promise<GatewayDeviceConfig>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { X509Certificate } from 'crypto';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
export const OFFICIAL_GATEWAY_URL = 'https://mcp-v2.hcu-lab.me/mcp';
|
|
5
|
+
function normalizedUrl(value) {
|
|
6
|
+
return new URL(value).toString();
|
|
7
|
+
}
|
|
8
|
+
function defaultOfficialCaPath() {
|
|
9
|
+
return fileURLToPath(new URL('../data/official-app-ca.pem', import.meta.url));
|
|
10
|
+
}
|
|
11
|
+
export function isOfficialGatewayUrl(value) {
|
|
12
|
+
if (!value)
|
|
13
|
+
return false;
|
|
14
|
+
try {
|
|
15
|
+
return normalizedUrl(value) === normalizedUrl(OFFICIAL_GATEWAY_URL);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function bootstrapOfficialGatewayTrust(store, options = {}) {
|
|
22
|
+
const current = await store.load();
|
|
23
|
+
const gatewayUrl = String(process.env.MCP_GATEWAY_URL || options.gatewayUrl || current.gatewayUrl || OFFICIAL_GATEWAY_URL).trim();
|
|
24
|
+
if (!isOfficialGatewayUrl(gatewayUrl)) {
|
|
25
|
+
if (isOfficialGatewayUrl(current.gatewayUrl) && current.appCaPem) {
|
|
26
|
+
return await store.update({ gatewayUrl, appCaPem: null });
|
|
27
|
+
}
|
|
28
|
+
return await store.update({ gatewayUrl });
|
|
29
|
+
}
|
|
30
|
+
if (current.appCaPem && current.securityProtocolFloor >= 2 && isOfficialGatewayUrl(current.gatewayUrl || gatewayUrl)) {
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
const caPath = options.caPath || defaultOfficialCaPath();
|
|
34
|
+
let appCaPem;
|
|
35
|
+
try {
|
|
36
|
+
appCaPem = await fs.readFile(caPath, 'utf8');
|
|
37
|
+
const certificate = new X509Certificate(appCaPem);
|
|
38
|
+
if (!certificate.ca)
|
|
39
|
+
throw new Error('certificate is not a CA');
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(`Official MCP Device application CA is not provisioned in this package; refusing network bootstrap: ${error?.message || error}`);
|
|
43
|
+
}
|
|
44
|
+
return await store.update({
|
|
45
|
+
gatewayUrl: OFFICIAL_GATEWAY_URL,
|
|
46
|
+
appCaPem,
|
|
47
|
+
securityProtocolFloor: 2
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function inspectProjectOnDevice(root: string, args?: any): Promise<any>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const DEFAULT_EXCLUDES = new Set(['.git', 'node_modules', 'logs', 'packages', '_zip_temp']);
|
|
8
|
+
const DEFAULT_TREE_LIMIT = 200;
|
|
9
|
+
const MAX_TREE_LIMIT = 500;
|
|
10
|
+
const DEFAULT_TREE_DEPTH = 3;
|
|
11
|
+
const MAX_TREE_DEPTH = 10;
|
|
12
|
+
const CURSOR_VERSION = 1;
|
|
13
|
+
const MAX_PROJECT_FILE_BYTES = 32 * 1024;
|
|
14
|
+
const VIEWS = new Set(['summary', 'tree', 'git_status', 'git_diff', 'readme', 'package', 'file']);
|
|
15
|
+
function boundedInteger(value, fallback, min, max, label) {
|
|
16
|
+
if (value === undefined || value === null || value === '')
|
|
17
|
+
return fallback;
|
|
18
|
+
const number = Number(value);
|
|
19
|
+
if (!Number.isInteger(number) || number < min || number > max) {
|
|
20
|
+
throw new Error(`Invalid ${label}: ${value}. Expected an integer from ${min} to ${max}.`);
|
|
21
|
+
}
|
|
22
|
+
return number;
|
|
23
|
+
}
|
|
24
|
+
function cursorVersion(projectId, depth) {
|
|
25
|
+
return createHash('sha256').update(`project-tree\n${projectId}\n${depth}`).digest('base64url').slice(0, 16);
|
|
26
|
+
}
|
|
27
|
+
function encodeCursor(offset, version) {
|
|
28
|
+
return Buffer.from(JSON.stringify({ v: CURSOR_VERSION, kind: 'project-tree', offset, version }), 'utf8').toString('base64url');
|
|
29
|
+
}
|
|
30
|
+
function decodeCursor(cursor, version) {
|
|
31
|
+
if (!cursor)
|
|
32
|
+
return 0;
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(Buffer.from(String(cursor), 'base64url').toString('utf8'));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new Error('Invalid cursor: expected an opaque cursor returned by the previous page.');
|
|
39
|
+
}
|
|
40
|
+
if (parsed?.v !== CURSOR_VERSION || parsed?.kind !== 'project-tree' || parsed?.version !== version || !Number.isInteger(parsed?.offset) || parsed.offset < 0) {
|
|
41
|
+
throw new Error('Invalid or stale cursor: request parameters or catalog version changed.');
|
|
42
|
+
}
|
|
43
|
+
return parsed.offset;
|
|
44
|
+
}
|
|
45
|
+
async function exists(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(filePath);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function resolveInsideRoot(root, relativePath) {
|
|
55
|
+
const relative = String(relativePath || '').trim();
|
|
56
|
+
if (!relative || path.isAbsolute(relative) || relative.split(/[\\/]+/).includes('..')) {
|
|
57
|
+
throw new Error('Invalid project-relative resource path.');
|
|
58
|
+
}
|
|
59
|
+
const canonicalRoot = await fs.realpath(root);
|
|
60
|
+
const candidate = await fs.realpath(path.resolve(canonicalRoot, relative));
|
|
61
|
+
const rel = path.relative(canonicalRoot, candidate);
|
|
62
|
+
if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
63
|
+
throw new Error('Resource path escapes project root.');
|
|
64
|
+
}
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
function looksBinary(buffer) {
|
|
68
|
+
return buffer.subarray(0, Math.min(buffer.length, 4096)).includes(0);
|
|
69
|
+
}
|
|
70
|
+
async function collectTreeEntries(root, maxDepth, stopAfter) {
|
|
71
|
+
const entries = [];
|
|
72
|
+
async function walk(dir, depth, relativeDir = '') {
|
|
73
|
+
if (entries.length >= stopAfter || depth > maxDepth)
|
|
74
|
+
return;
|
|
75
|
+
const dirEntries = (await fs.readdir(dir, { withFileTypes: true }))
|
|
76
|
+
.filter(entry => !DEFAULT_EXCLUDES.has(entry.name))
|
|
77
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
78
|
+
for (const entry of dirEntries) {
|
|
79
|
+
if (entries.length >= stopAfter)
|
|
80
|
+
return;
|
|
81
|
+
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
82
|
+
const isDirectory = entry.isDirectory();
|
|
83
|
+
entries.push({ path: relativePath, name: entry.name, type: isDirectory ? 'directory' : 'file', depth });
|
|
84
|
+
if (isDirectory && depth < maxDepth)
|
|
85
|
+
await walk(path.join(dir, entry.name), depth + 1, relativePath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await walk(root, 1);
|
|
89
|
+
return entries;
|
|
90
|
+
}
|
|
91
|
+
async function execGitRead(cwd, args) {
|
|
92
|
+
try {
|
|
93
|
+
const { stdout, stderr } = await execFileAsync('git', args, { cwd, timeout: 30000, windowsHide: true, maxBuffer: 1024 * 1024, encoding: 'utf8' });
|
|
94
|
+
return { ok: true, stdout: String(stdout || ''), stderr: String(stderr || ''), exitCode: 0 };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
stdout: String(error?.stdout || ''),
|
|
100
|
+
stderr: String(error?.stderr || error?.message || ''),
|
|
101
|
+
exitCode: Number.isInteger(error?.code) ? error.code : 1
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export async function inspectProjectOnDevice(root, args = {}) {
|
|
106
|
+
const view = String(args.view || 'summary');
|
|
107
|
+
if (!VIEWS.has(view))
|
|
108
|
+
throw new Error(`Invalid project inspection view: ${view}.`);
|
|
109
|
+
const projectId = String(args.project_id || '').trim();
|
|
110
|
+
if (view === 'summary') {
|
|
111
|
+
return {
|
|
112
|
+
defaultRootName: path.basename(root) || projectId,
|
|
113
|
+
hasPackageJson: await exists(path.join(root, 'package.json')),
|
|
114
|
+
hasReadme: await exists(path.join(root, 'README.md')) || await exists(path.join(root, 'README.vi.md'))
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (view === 'tree') {
|
|
118
|
+
const depth = boundedInteger(args.depth, DEFAULT_TREE_DEPTH, 1, MAX_TREE_DEPTH, 'depth');
|
|
119
|
+
const limit = boundedInteger(args.limit, DEFAULT_TREE_LIMIT, 1, MAX_TREE_LIMIT, 'limit');
|
|
120
|
+
const version = cursorVersion(projectId, depth);
|
|
121
|
+
const offset = decodeCursor(args.cursor, version);
|
|
122
|
+
const entries = await collectTreeEntries(root, depth, offset + limit + 1);
|
|
123
|
+
if (offset > entries.length)
|
|
124
|
+
throw new Error('Invalid or stale cursor: tree offset is outside the current result set.');
|
|
125
|
+
const page = entries.slice(offset, offset + limit);
|
|
126
|
+
const truncated = entries.length > offset + page.length;
|
|
127
|
+
return {
|
|
128
|
+
rootName: path.basename(root),
|
|
129
|
+
maxDepth: depth,
|
|
130
|
+
maxEntries: limit,
|
|
131
|
+
entries: page,
|
|
132
|
+
truncated,
|
|
133
|
+
nextCursor: truncated ? encodeCursor(offset + page.length, version) : null
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (view === 'git_status') {
|
|
137
|
+
const result = await execGitRead(root, ['status', '--short', '--branch']);
|
|
138
|
+
return { ok: result.ok, status: result.stdout, stderr: result.stderr, exitCode: result.exitCode };
|
|
139
|
+
}
|
|
140
|
+
if (view === 'git_diff') {
|
|
141
|
+
const staged = args.staged === true;
|
|
142
|
+
const result = await execGitRead(root, staged ? ['diff', '--staged'] : ['diff']);
|
|
143
|
+
return { ok: result.ok, staged, text: result.ok ? result.stdout : result.stderr, stderr: result.stderr, exitCode: result.exitCode };
|
|
144
|
+
}
|
|
145
|
+
if (view === 'readme') {
|
|
146
|
+
for (const fileName of ['README.md', 'README.vi.md']) {
|
|
147
|
+
try {
|
|
148
|
+
const filePath = await resolveInsideRoot(root, fileName);
|
|
149
|
+
return { fileName, text: await fs.readFile(filePath, 'utf8') };
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error?.code !== 'ENOENT')
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`README not found for project_id: ${projectId}`);
|
|
157
|
+
}
|
|
158
|
+
if (view === 'package') {
|
|
159
|
+
let packagePath;
|
|
160
|
+
try {
|
|
161
|
+
packagePath = await resolveInsideRoot(root, 'package.json');
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error?.code === 'ENOENT')
|
|
165
|
+
throw new Error(`package.json not found for project_id: ${projectId}`);
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
return { data: JSON.parse(await fs.readFile(packagePath, 'utf8')) };
|
|
169
|
+
}
|
|
170
|
+
const filePath = await resolveInsideRoot(root, args.relative_path);
|
|
171
|
+
const stat = await fs.stat(filePath);
|
|
172
|
+
if (!stat.isFile())
|
|
173
|
+
throw new Error('Resource file path must point to a regular file.');
|
|
174
|
+
if (stat.size > MAX_PROJECT_FILE_BYTES)
|
|
175
|
+
throw new Error(`Resource file is too large for text preview: ${stat.size} bytes.`);
|
|
176
|
+
const buffer = await fs.readFile(filePath);
|
|
177
|
+
if (looksBinary(buffer))
|
|
178
|
+
throw new Error('Resource file appears to be binary; text resources only support textual files.');
|
|
179
|
+
return { text: buffer.toString('utf8') };
|
|
180
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type DeviceStatePaths } from './device-state.js';
|
|
2
|
+
export type RuntimeMode = 'foreground' | 'windows' | 'systemd' | 'pm2' | 'bootstrap';
|
|
3
|
+
export interface RuntimeOwnerStatus {
|
|
4
|
+
product: 'MCP Device';
|
|
5
|
+
mode: RuntimeMode;
|
|
6
|
+
pid: number;
|
|
7
|
+
startedAt: number;
|
|
8
|
+
executable: string;
|
|
9
|
+
entrypoint: string;
|
|
10
|
+
deviceId: string | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function probeRuntimeOwner(paths?: DeviceStatePaths, platform?: NodeJS.Platform | string): Promise<RuntimeOwnerStatus | null>;
|
|
13
|
+
export declare class RuntimeOwner {
|
|
14
|
+
readonly paths: DeviceStatePaths;
|
|
15
|
+
readonly endpoint: string;
|
|
16
|
+
readonly mode: RuntimeMode;
|
|
17
|
+
private readonly platform;
|
|
18
|
+
private readonly nonce;
|
|
19
|
+
private readonly status;
|
|
20
|
+
private server?;
|
|
21
|
+
private onStop?;
|
|
22
|
+
constructor({ paths, platform, mode, onStop }: {
|
|
23
|
+
paths?: DeviceStatePaths;
|
|
24
|
+
platform?: NodeJS.Platform | string;
|
|
25
|
+
mode: RuntimeMode;
|
|
26
|
+
onStop?: () => Promise<void> | void;
|
|
27
|
+
});
|
|
28
|
+
acquire(): Promise<void>;
|
|
29
|
+
bootstrap(): Promise<void>;
|
|
30
|
+
setDeviceId(deviceId: string): void;
|
|
31
|
+
release(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare function stopRuntimeOwner(paths?: DeviceStatePaths, platform?: NodeJS.Platform | string): Promise<boolean>;
|
|
34
|
+
export declare function acquireRuntimeOwner(owner: RuntimeOwner, { confirmTakeover, waitMs, pollMs }?: {
|
|
35
|
+
confirmTakeover?: (existing: RuntimeOwnerStatus) => Promise<boolean> | boolean;
|
|
36
|
+
waitMs?: number;
|
|
37
|
+
pollMs?: number;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
takenOver: boolean;
|
|
40
|
+
previous: RuntimeOwnerStatus | null;
|
|
41
|
+
}>;
|