@livedesk/client 0.1.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.
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { chmodSync, existsSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawn, spawnSync } from 'node:child_process';
7
+ import os from 'node:os';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const packageRoot = resolve(__dirname, '..');
11
+ const nodeAgentPath = join(__dirname, 'livedesk-client-node.js');
12
+ const FAST_PREFLIGHT_TIMEOUT_MS = 5000;
13
+
14
+ function printHelp() {
15
+ const result = spawnSync(process.execPath, [nodeAgentPath, '--help'], {
16
+ encoding: 'utf8'
17
+ });
18
+ const help = result.stdout || '';
19
+ process.stdout.write(`${help}
20
+
21
+ Engine selection:
22
+ --engine auto|fast|csharp|node Select agent engine. Default: auto.
23
+ --fast Force C# RemoteFast frame.binary engine.
24
+ --node Force the legacy Node agent.
25
+ --control Enable RemoteFast keyboard/mouse control.
26
+ --no-control Disable RemoteFast keyboard/mouse control.
27
+ --transport ws|tcp RemoteFast hub transport. Default: ws.
28
+
29
+ Auto uses C# RemoteFast when supported and falls back to Node for AI assist or
30
+ when a packaged RemoteFast runtime is unavailable.
31
+ `);
32
+ }
33
+
34
+ function normalizeEngine(value) {
35
+ const engine = String(value || 'auto').trim().toLowerCase();
36
+ if (engine === 'c#' || engine === 'csharp' || engine === 'fast') {
37
+ return 'fast';
38
+ }
39
+ if (engine === 'js' || engine === 'node' || engine === 'legacy') {
40
+ return 'node';
41
+ }
42
+ return 'auto';
43
+ }
44
+
45
+ function parseLauncherArgs(argv) {
46
+ const forwarded = [];
47
+ let engine = normalizeEngine(process.env.LIVEDESK_CLIENT_ENGINE || process.env.MINDEXEC_REMOTE_ENGINE);
48
+ let help = false;
49
+ let nodeOnlyFeature = false;
50
+ let fakeThumbnail = false;
51
+
52
+ for (let index = 0; index < argv.length; index += 1) {
53
+ const arg = argv[index];
54
+ if (arg === '--engine') {
55
+ engine = normalizeEngine(argv[index + 1]);
56
+ index += 1;
57
+ continue;
58
+ }
59
+ if (arg === '--fast' || arg === '--csharp') {
60
+ engine = 'fast';
61
+ continue;
62
+ }
63
+ if (arg === '--node') {
64
+ engine = 'node';
65
+ continue;
66
+ }
67
+
68
+ if (arg === '--help' || arg === '-h') {
69
+ help = true;
70
+ }
71
+ if (arg === '--ai' || arg === '--fake-ai' || arg === '--ai-model' || arg === '--no-ai') {
72
+ nodeOnlyFeature = true;
73
+ }
74
+ if (arg === '--fake-thumbnail') {
75
+ fakeThumbnail = true;
76
+ }
77
+ forwarded.push(arg);
78
+ }
79
+
80
+ return {
81
+ engine,
82
+ forwarded,
83
+ help,
84
+ nodeOnlyFeature,
85
+ fakeThumbnail
86
+ };
87
+ }
88
+
89
+ function getFastRuntime() {
90
+ const platform = os.platform();
91
+ const arch = os.arch();
92
+ if (platform === 'win32' && arch === 'x64') {
93
+ return {
94
+ rid: 'win-x64',
95
+ executable: join(packageRoot, 'fast', 'win-x64', 'mindexec-remote-fast.exe'),
96
+ dll: join(packageRoot, 'fast', 'win-x64', 'mindexec-remote-fast.dll')
97
+ };
98
+ }
99
+ if (platform === 'darwin' && arch === 'arm64') {
100
+ return {
101
+ rid: 'osx-arm64',
102
+ executable: join(packageRoot, 'fast', 'osx-arm64', 'mindexec-remote-fast'),
103
+ dll: join(packageRoot, 'fast', 'osx-arm64', 'mindexec-remote-fast.dll')
104
+ };
105
+ }
106
+ if (platform === 'darwin' && arch === 'x64') {
107
+ return {
108
+ rid: 'osx-x64',
109
+ executable: join(packageRoot, 'fast', 'osx-x64', 'mindexec-remote-fast'),
110
+ dll: join(packageRoot, 'fast', 'osx-x64', 'mindexec-remote-fast.dll')
111
+ };
112
+ }
113
+
114
+ return null;
115
+ }
116
+
117
+ function summarizeText(value, maxLength = 600) {
118
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
119
+ if (text.length <= maxLength) {
120
+ return text;
121
+ }
122
+
123
+ return `${text.slice(0, maxLength - 3)}...`;
124
+ }
125
+
126
+ function getDotnetRuntimeStatus() {
127
+ if (process.env.LIVEDESK_CLIENT_FAST_DISABLE_DOTNET === '1' || process.env.MINDEXEC_REMOTE_FAST_DISABLE_DOTNET === '1') {
128
+ return { ok: false, reason: 'dotnet disabled by LIVEDESK_CLIENT_FAST_DISABLE_DOTNET' };
129
+ }
130
+
131
+ const result = spawnSync('dotnet', ['--list-runtimes'], {
132
+ encoding: 'utf8',
133
+ windowsHide: true,
134
+ timeout: FAST_PREFLIGHT_TIMEOUT_MS
135
+ });
136
+ if (result.error) {
137
+ return { ok: false, reason: result.error.message || 'dotnet runtime check failed' };
138
+ }
139
+
140
+ if (result.status !== 0) {
141
+ return {
142
+ ok: false,
143
+ reason: summarizeText(result.stderr || result.stdout || `dotnet exited with ${result.status}`)
144
+ };
145
+ }
146
+
147
+ if (!/Microsoft\.NETCore\.App\s+9\./i.test(result.stdout || '')) {
148
+ return { ok: false, reason: '.NET 9 runtime is not installed' };
149
+ }
150
+
151
+ return { ok: true, reason: 'ok' };
152
+ }
153
+
154
+ function hasDotnetRuntime() {
155
+ return getDotnetRuntimeStatus().ok;
156
+ }
157
+
158
+ function hasFastExecutable(runtime) {
159
+ return !!runtime?.executable && existsSync(runtime.executable);
160
+ }
161
+
162
+ function hasFastDll(runtime) {
163
+ return !!runtime?.dll && existsSync(runtime.dll);
164
+ }
165
+
166
+ function describePreflightFailure(result) {
167
+ if (result?.error) {
168
+ return result.error.message || String(result.error);
169
+ }
170
+
171
+ const exitText = result?.signal
172
+ ? `signal:${result.signal}`
173
+ : `exit:${Number.isFinite(result?.status) ? result.status : 'unknown'}`;
174
+ return summarizeText(result?.stderr || result?.stdout || exitText);
175
+ }
176
+
177
+ function prepareFastExecutable(executable) {
178
+ if (os.platform() === 'win32') {
179
+ return;
180
+ }
181
+
182
+ try {
183
+ chmodSync(executable, 0o755);
184
+ } catch {
185
+ // npm normally preserves executable bits; chmod is only a best-effort repair.
186
+ }
187
+ }
188
+
189
+ function buildFastArgs(args, fakeThumbnail) {
190
+ const forwarded = [];
191
+ for (const arg of args) {
192
+ if (arg === '--fake-thumbnail') {
193
+ forwarded.push('--fake-frame');
194
+ continue;
195
+ }
196
+ if (arg === '--tasks' || arg === '--no-tasks' || arg === '--no-ai') {
197
+ continue;
198
+ }
199
+ forwarded.push(arg);
200
+ }
201
+ if (fakeThumbnail && !forwarded.includes('--fake-frame')) {
202
+ forwarded.push('--fake-frame');
203
+ }
204
+ return forwarded;
205
+ }
206
+
207
+ function resolveFastLaunch(runtime) {
208
+ if (!runtime) {
209
+ return {
210
+ ok: false,
211
+ reason: 'C# RemoteFast is not packaged for this OS/architecture'
212
+ };
213
+ }
214
+
215
+ const failures = [];
216
+ if (hasFastExecutable(runtime)) {
217
+ prepareFastExecutable(runtime.executable);
218
+ const result = spawnSync(runtime.executable, ['--version'], {
219
+ encoding: 'utf8',
220
+ windowsHide: true,
221
+ timeout: FAST_PREFLIGHT_TIMEOUT_MS
222
+ });
223
+ if (result.status === 0) {
224
+ return {
225
+ ok: true,
226
+ command: runtime.executable,
227
+ argsPrefix: []
228
+ };
229
+ }
230
+
231
+ failures.push(`apphost: ${describePreflightFailure(result)}`);
232
+ }
233
+
234
+ if (hasFastDll(runtime)) {
235
+ const dotnet = getDotnetRuntimeStatus();
236
+ if (dotnet.ok) {
237
+ return {
238
+ ok: true,
239
+ command: 'dotnet',
240
+ argsPrefix: [runtime.dll]
241
+ };
242
+ }
243
+
244
+ failures.push(`dotnet: ${dotnet.reason}`);
245
+ }
246
+
247
+ return {
248
+ ok: false,
249
+ reason: failures.length > 0
250
+ ? failures.join('; ')
251
+ : `C# RemoteFast runtime files are missing for ${runtime.rid}`
252
+ };
253
+ }
254
+
255
+ function spawnAgent(command, args) {
256
+ const child = spawn(command, args, {
257
+ stdio: 'inherit',
258
+ windowsHide: true
259
+ });
260
+
261
+ child.once('error', error => {
262
+ console.error(`Failed to launch ${command}: ${error.message}`);
263
+ process.exitCode = 1;
264
+ });
265
+ child.once('exit', (code, signal) => {
266
+ if (signal) {
267
+ process.kill(process.pid, signal);
268
+ return;
269
+ }
270
+ process.exit(code ?? 0);
271
+ });
272
+ }
273
+
274
+ function shouldUseFast(parsed) {
275
+ if (parsed.engine === 'node') {
276
+ return false;
277
+ }
278
+ if (parsed.engine === 'fast') {
279
+ return true;
280
+ }
281
+ return !parsed.nodeOnlyFeature;
282
+ }
283
+
284
+ const parsed = parseLauncherArgs(process.argv.slice(2));
285
+ if (parsed.help) {
286
+ printHelp();
287
+ process.exit(0);
288
+ }
289
+
290
+ const fastRuntime = getFastRuntime();
291
+ const useFast = shouldUseFast(parsed);
292
+ if (useFast) {
293
+ const fastArgs = buildFastArgs(parsed.forwarded, parsed.fakeThumbnail);
294
+ const fastLaunch = resolveFastLaunch(fastRuntime);
295
+ if (fastLaunch.ok) {
296
+ spawnAgent(fastLaunch.command, [...fastLaunch.argsPrefix, ...fastArgs]);
297
+ } else if (parsed.engine === 'fast') {
298
+ console.error(`C# RemoteFast is unavailable: ${fastLaunch.reason}. Use --engine node to run the legacy Node agent.`);
299
+ process.exit(2);
300
+ } else {
301
+ console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
302
+ spawnAgent(process.execPath, [nodeAgentPath, ...parsed.forwarded]);
303
+ }
304
+ } else {
305
+ spawnAgent(process.execPath, [nodeAgentPath, ...parsed.forwarded]);
306
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "runtimeTarget": {
3
+ "name": ".NETCoreApp,Version=v9.0/osx-arm64",
4
+ "signature": ""
5
+ },
6
+ "compilationOptions": {},
7
+ "targets": {
8
+ ".NETCoreApp,Version=v9.0": {},
9
+ ".NETCoreApp,Version=v9.0/osx-arm64": {
10
+ "mindexec-remote-fast/1.0.0": {
11
+ "runtime": {
12
+ "mindexec-remote-fast.dll": {}
13
+ }
14
+ }
15
+ }
16
+ },
17
+ "libraries": {
18
+ "mindexec-remote-fast/1.0.0": {
19
+ "type": "project",
20
+ "serviceable": false,
21
+ "sha512": ""
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "runtimeOptions": {
3
+ "tfm": "net9.0",
4
+ "framework": {
5
+ "name": "Microsoft.NETCore.App",
6
+ "version": "9.0.0"
7
+ },
8
+ "configProperties": {
9
+ "System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
10
+ "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "runtimeTarget": {
3
+ "name": ".NETCoreApp,Version=v9.0/osx-x64",
4
+ "signature": ""
5
+ },
6
+ "compilationOptions": {},
7
+ "targets": {
8
+ ".NETCoreApp,Version=v9.0": {},
9
+ ".NETCoreApp,Version=v9.0/osx-x64": {
10
+ "mindexec-remote-fast/1.0.0": {
11
+ "runtime": {
12
+ "mindexec-remote-fast.dll": {}
13
+ }
14
+ }
15
+ }
16
+ },
17
+ "libraries": {
18
+ "mindexec-remote-fast/1.0.0": {
19
+ "type": "project",
20
+ "serviceable": false,
21
+ "sha512": ""
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "runtimeOptions": {
3
+ "tfm": "net9.0",
4
+ "framework": {
5
+ "name": "Microsoft.NETCore.App",
6
+ "version": "9.0.0"
7
+ },
8
+ "configProperties": {
9
+ "System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
10
+ "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "runtimeTarget": {
3
+ "name": ".NETCoreApp,Version=v9.0/win-x64",
4
+ "signature": ""
5
+ },
6
+ "compilationOptions": {},
7
+ "targets": {
8
+ ".NETCoreApp,Version=v9.0": {},
9
+ ".NETCoreApp,Version=v9.0/win-x64": {
10
+ "mindexec-remote-fast/1.0.0": {
11
+ "runtime": {
12
+ "mindexec-remote-fast.dll": {}
13
+ }
14
+ }
15
+ }
16
+ },
17
+ "libraries": {
18
+ "mindexec-remote-fast/1.0.0": {
19
+ "type": "project",
20
+ "serviceable": false,
21
+ "sha512": ""
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "runtimeOptions": {
3
+ "tfm": "net9.0",
4
+ "frameworks": [
5
+ {
6
+ "name": "Microsoft.NETCore.App",
7
+ "version": "9.0.0"
8
+ },
9
+ {
10
+ "name": "Microsoft.WindowsDesktop.App",
11
+ "version": "9.0.0"
12
+ }
13
+ ],
14
+ "configProperties": {
15
+ "System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
16
+ "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
17
+ "CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
18
+ }
19
+ }
20
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@livedesk/client",
3
+ "version": "0.1.0",
4
+ "description": "LiveDesk local remote client",
5
+ "type": "module",
6
+ "bin": {
7
+ "livedesk-client": "bin/livedesk-client.js",
8
+ "livedesk-client-node": "bin/livedesk-client-node.js",
9
+ "livedesk-client-fast": "bin/livedesk-client-fast.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "fast/",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "check": "node --check bin/livedesk-client.js && node --check bin/livedesk-client-node.js && node --check bin/livedesk-client-fast.js",
18
+ "pack:dry": "npm pack --dry-run"
19
+ },
20
+ "keywords": [
21
+ "livedesk",
22
+ "remote",
23
+ "agent",
24
+ "local",
25
+ "desktop"
26
+ ],
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "dependencies": {
32
+ "node-screenshots": "^0.2.8",
33
+ "openai": "^6.42.0"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }