@livedesk/client 0.1.0 → 0.1.2

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 CHANGED
@@ -3,18 +3,26 @@
3
3
  Run a direct LiveDesk remote client from a controlled computer.
4
4
 
5
5
  ```powershell
6
- npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token>
6
+ npx @livedesk/client connect
7
7
  ```
8
8
 
9
- For LAN use, start `@livedesk/hub` with an externally reachable RemoteHub host
10
- and point clients at that address:
9
+ The default flow opens Google sign-in, finds the active LiveDesk manager
10
+ published by the same account, and asks for a computer number to place this
11
+ machine on the screen wall.
12
+
13
+ On the manager computer, open the LiveDesk dashboard and sign in first. The
14
+ dashboard keeps its local hub address and private pair token refreshed in the
15
+ LiveDesk Supabase registry for the signed-in account.
16
+
17
+ For manual LAN fallback, start `@livedesk/hub` with an externally reachable
18
+ RemoteHub host and pass the address/token explicitly:
11
19
 
12
20
  ```powershell
13
21
  $env:REMOTE_HUB_HOST="0.0.0.0"
14
22
  $env:REMOTE_HUB_PORT="5197"
15
23
  npm run dev:hub
16
24
 
17
- npx @livedesk/client connect --manager 192.168.0.10:5197 --pair <strong-token>
25
+ npx @livedesk/client connect --no-login --manager 192.168.0.10:5197 --pair <strong-token> --slot 2
18
26
  ```
19
27
 
20
28
  The client registers the device, sends status heartbeats, can return
@@ -28,9 +36,10 @@ is unavailable.
28
36
  Useful flags:
29
37
 
30
38
  ```powershell
31
- npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token> --no-thumbnail
32
- npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token> --no-live
33
- npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token> --engine node
34
- npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token> --engine fast
35
- npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token> --engine fast --trace-frames
39
+ npx @livedesk/client connect --slot 3
40
+ npx @livedesk/client connect --no-thumbnail
41
+ npx @livedesk/client connect --no-live
42
+ npx @livedesk/client connect --engine node
43
+ npx @livedesk/client connect --engine fast --trace-frames
44
+ npx @livedesk/client connect --logout
36
45
  ```
@@ -25,6 +25,7 @@ Usage:
25
25
  Options:
26
26
  --manager <host:port> RemoteHub address. Default: ${DEFAULT_MANAGER}
27
27
  --pair <token> RemoteHub pairing token. Can also use LIVEDESK_CLIENT_PAIR_TOKEN.
28
+ --slot <number> Screen wall slot number for this computer.
28
29
  --name <name> Friendly device name. Default: OS hostname.
29
30
  --heartbeat <ms> Status heartbeat interval. Default: ${DEFAULT_HEARTBEAT_MS}
30
31
  --device-id <id> Stable device id. Default: generated and saved per OS user.
@@ -60,6 +61,7 @@ function parseArgs(argv) {
60
61
  command: 'connect',
61
62
  manager: process.env.LIVEDESK_CLIENT_MANAGER || process.env.MINDEXEC_REMOTE_MANAGER || DEFAULT_MANAGER,
62
63
  pair: process.env.LIVEDESK_CLIENT_PAIR_TOKEN || process.env.MINDEXEC_REMOTE_PAIR_TOKEN || '',
64
+ slotNumber: normalizeSlotNumber(process.env.LIVEDESK_CLIENT_SLOT || process.env.MINDEXEC_REMOTE_SLOT),
63
65
  name: process.env.LIVEDESK_CLIENT_NAME || process.env.MINDEXEC_REMOTE_NAME || os.hostname(),
64
66
  heartbeatMs: DEFAULT_HEARTBEAT_MS,
65
67
  deviceId: '',
@@ -91,6 +93,9 @@ function parseArgs(argv) {
91
93
  case '--pair':
92
94
  result.pair = args[++index] || '';
93
95
  break;
96
+ case '--slot':
97
+ result.slotNumber = normalizeSlotNumber(args[++index] || result.slotNumber);
98
+ break;
94
99
  case '--name':
95
100
  result.name = args[++index] || result.name;
96
101
  break;
@@ -186,6 +191,11 @@ function normalizeDeviceId(value) {
186
191
  return String(value || '').trim().replace(/[^a-zA-Z0-9_.:-]/g, '-').slice(0, 128);
187
192
  }
188
193
 
194
+ function normalizeSlotNumber(value) {
195
+ const number = Number(String(value || '').trim());
196
+ return Number.isInteger(number) && number >= 1 && number <= 99 ? number : 0;
197
+ }
198
+
189
199
  async function getDeviceId(explicitDeviceId = '') {
190
200
  const explicit = normalizeDeviceId(explicitDeviceId);
191
201
  if (explicit) {
@@ -213,10 +223,10 @@ async function getDeviceId(explicitDeviceId = '') {
213
223
  return deviceId;
214
224
  }
215
225
 
216
- function getStatus() {
226
+ function getStatus(options = {}) {
217
227
  const totalMem = os.totalmem();
218
228
  const freeMem = os.freemem();
219
- return {
229
+ const status = {
220
230
  uptimeSec: Math.round(os.uptime()),
221
231
  loadavg: os.loadavg(),
222
232
  totalMem,
@@ -226,6 +236,10 @@ function getStatus() {
226
236
  release: os.release(),
227
237
  timestamp: new Date().toISOString()
228
238
  };
239
+ if (options.slotNumber) {
240
+ status.slotNumber = options.slotNumber;
241
+ }
242
+ return status;
229
243
  }
230
244
 
231
245
  function writeJsonLine(socket, payload) {
@@ -797,6 +811,7 @@ function connectOnce(options, deviceId) {
797
811
  pairToken: options.pair,
798
812
  deviceId,
799
813
  deviceName: options.name,
814
+ slotNumber: options.slotNumber || undefined,
800
815
  hostname: os.hostname(),
801
816
  platform: os.platform(),
802
817
  arch: os.arch(),
@@ -833,7 +848,7 @@ function connectOnce(options, deviceId) {
833
848
  console.log(`Connected to RemoteHub as ${options.name} (${deviceId})`);
834
849
  writeJsonLine(socket, {
835
850
  type: 'status',
836
- status: getStatus()
851
+ status: getStatus(options)
837
852
  });
838
853
  if (options.once) {
839
854
  finish();
@@ -843,7 +858,7 @@ function connectOnce(options, deviceId) {
843
858
  heartbeatTimer = setInterval(() => {
844
859
  writeJsonLine(socket, {
845
860
  type: 'status',
846
- status: getStatus()
861
+ status: getStatus(options)
847
862
  });
848
863
  }, options.heartbeatMs);
849
864
  } else if (message.type === 'command') {
@@ -1,34 +1,61 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { chmodSync, existsSync } from 'node:fs';
3
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { spawn, spawnSync } from 'node:child_process';
7
+ import { createServer } from 'node:http';
8
+ import net from 'node:net';
7
9
  import os from 'node:os';
10
+ import { createInterface } from 'node:readline/promises';
8
11
 
9
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
13
  const packageRoot = resolve(__dirname, '..');
11
14
  const nodeAgentPath = join(__dirname, 'livedesk-client-node.js');
12
15
  const FAST_PREFLIGHT_TIMEOUT_MS = 5000;
16
+ const DEFAULT_MANAGER = '127.0.0.1:5197';
17
+ const SUPABASE_URL = process.env.LIVEDESK_SUPABASE_URL || 'https://otbyfkjxrkngvjziawki.supabase.co';
18
+ const SUPABASE_PUBLISHABLE_KEY = process.env.LIVEDESK_SUPABASE_PUBLISHABLE_KEY || 'sb_publishable_NpUs0RDJH2YnllsqTKO6TQ_1jTdSsNQ';
19
+ const CLIENT_STATE_DIR = join(os.homedir(), '.livedesk-client');
20
+ const CLIENT_AUTH_PATH = join(CLIENT_STATE_DIR, 'auth.json');
21
+ const CLIENT_AUTH_STORAGE_KEY = 'livedesk.client.supabase.auth';
13
22
 
14
23
  function printHelp() {
15
- const result = spawnSync(process.execPath, [nodeAgentPath, '--help'], {
16
- encoding: 'utf8'
17
- });
18
- const help = result.stdout || '';
19
- process.stdout.write(`${help}
24
+ process.stdout.write(`
25
+ LiveDesk Client
26
+
27
+ Usage:
28
+ npx @livedesk/client connect
20
29
 
21
- Engine selection:
30
+ Default flow:
31
+ Opens Google sign-in, finds the signed-in manager, asks for this computer's
32
+ screen-wall number, then connects locally.
33
+
34
+ Options:
35
+ --slot <number> Screen wall slot number for this computer.
36
+ --name <name> Friendly device name. Default: OS hostname.
22
37
  --engine auto|fast|csharp|node Select agent engine. Default: auto.
23
38
  --fast Force C# RemoteFast frame.binary engine.
24
39
  --node Force the legacy Node agent.
25
40
  --control Enable RemoteFast keyboard/mouse control.
26
41
  --no-control Disable RemoteFast keyboard/mouse control.
27
- --transport ws|tcp RemoteFast hub transport. Default: ws.
42
+ --thumbnail Enable thumbnail capture when supported.
43
+ --no-thumbnail Disable thumbnail capture capability.
44
+ --live Enable focused live screen streaming.
45
+ --no-live Disable focused live screen streaming.
46
+ --tasks Enable safe remote task inbox.
47
+ --no-tasks Disable remote task dispatch capability.
48
+ --login Sign in with Google and auto-discover manager.
49
+ --logout Forget saved Google session before connecting.
50
+ --version Show the agent version.
51
+ --help Show this help.
52
+
53
+ Manual fallback:
54
+ npx @livedesk/client connect --no-login --manager 192.168.0.10:5197 --pair <token> --slot 2
28
55
 
29
56
  Auto uses C# RemoteFast when supported and falls back to Node for AI assist or
30
57
  when a packaged RemoteFast runtime is unavailable.
31
- `);
58
+ `.trimStart());
32
59
  }
33
60
 
34
61
  function normalizeEngine(value) {
@@ -46,11 +73,27 @@ function parseLauncherArgs(argv) {
46
73
  const forwarded = [];
47
74
  let engine = normalizeEngine(process.env.LIVEDESK_CLIENT_ENGINE || process.env.MINDEXEC_REMOTE_ENGINE);
48
75
  let help = false;
76
+ let loginRequested = false;
77
+ let loginDisabled = false;
78
+ let logout = false;
79
+ let manager = process.env.LIVEDESK_CLIENT_MANAGER || process.env.MINDEXEC_REMOTE_MANAGER || '';
80
+ let pair = process.env.LIVEDESK_CLIENT_PAIR_TOKEN || process.env.MINDEXEC_REMOTE_PAIR_TOKEN || '';
81
+ let slot = process.env.LIVEDESK_CLIENT_SLOT || process.env.MINDEXEC_REMOTE_SLOT || '';
82
+ let command = 'connect';
49
83
  let nodeOnlyFeature = false;
50
84
  let fakeThumbnail = false;
51
85
 
86
+ const args = [...argv];
87
+ if (args[0] && !args[0].startsWith('-')) {
88
+ command = args[0];
89
+ }
90
+
52
91
  for (let index = 0; index < argv.length; index += 1) {
53
92
  const arg = argv[index];
93
+ if (index === 0 && arg && !arg.startsWith('-')) {
94
+ forwarded.push(arg);
95
+ continue;
96
+ }
54
97
  if (arg === '--engine') {
55
98
  engine = normalizeEngine(argv[index + 1]);
56
99
  index += 1;
@@ -64,6 +107,36 @@ function parseLauncherArgs(argv) {
64
107
  engine = 'node';
65
108
  continue;
66
109
  }
110
+ if (arg === '--login') {
111
+ loginRequested = true;
112
+ continue;
113
+ }
114
+ if (arg === '--no-login') {
115
+ loginDisabled = true;
116
+ continue;
117
+ }
118
+ if (arg === '--logout') {
119
+ logout = true;
120
+ continue;
121
+ }
122
+ if (arg === '--manager') {
123
+ manager = argv[index + 1] || manager;
124
+ forwarded.push(arg, argv[index + 1]);
125
+ index += 1;
126
+ continue;
127
+ }
128
+ if (arg === '--pair') {
129
+ pair = argv[index + 1] || pair;
130
+ forwarded.push(arg, argv[index + 1]);
131
+ index += 1;
132
+ continue;
133
+ }
134
+ if (arg === '--slot') {
135
+ slot = argv[index + 1] || slot;
136
+ forwarded.push(arg, argv[index + 1]);
137
+ index += 1;
138
+ continue;
139
+ }
67
140
 
68
141
  if (arg === '--help' || arg === '-h') {
69
142
  help = true;
@@ -81,11 +154,304 @@ function parseLauncherArgs(argv) {
81
154
  engine,
82
155
  forwarded,
83
156
  help,
157
+ command,
158
+ loginRequested,
159
+ loginDisabled,
160
+ logout,
161
+ manager,
162
+ pair,
163
+ slot,
84
164
  nodeOnlyFeature,
85
165
  fakeThumbnail
86
166
  };
87
167
  }
88
168
 
169
+ function normalizeSlotNumber(value) {
170
+ const number = Number(String(value || '').trim());
171
+ if (!Number.isInteger(number) || number < 1 || number > 99) {
172
+ return '';
173
+ }
174
+ return String(number);
175
+ }
176
+
177
+ function upsertForwardedOption(args, flag, value) {
178
+ if (!value) {
179
+ return args;
180
+ }
181
+ const next = [...args];
182
+ const index = next.indexOf(flag);
183
+ if (index >= 0) {
184
+ next[index + 1] = value;
185
+ return next;
186
+ }
187
+ next.push(flag, value);
188
+ return next;
189
+ }
190
+
191
+ function createFileStorage(filePath) {
192
+ function readState() {
193
+ try {
194
+ return JSON.parse(readFileSync(filePath, 'utf8'));
195
+ } catch {
196
+ return {};
197
+ }
198
+ }
199
+
200
+ function writeState(state) {
201
+ mkdirSync(dirname(filePath), { recursive: true });
202
+ writeFileSync(filePath, JSON.stringify(state, null, 2));
203
+ }
204
+
205
+ return {
206
+ getItem(key) {
207
+ const state = readState();
208
+ return typeof state[key] === 'string' ? state[key] : null;
209
+ },
210
+ setItem(key, value) {
211
+ const state = readState();
212
+ state[key] = String(value);
213
+ writeState(state);
214
+ },
215
+ removeItem(key) {
216
+ const state = readState();
217
+ delete state[key];
218
+ writeState(state);
219
+ }
220
+ };
221
+ }
222
+
223
+ async function createSupabaseClient() {
224
+ const { createClient } = await import('@supabase/supabase-js');
225
+ return createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
226
+ auth: {
227
+ autoRefreshToken: false,
228
+ persistSession: true,
229
+ detectSessionInUrl: false,
230
+ flowType: 'pkce',
231
+ storageKey: CLIENT_AUTH_STORAGE_KEY,
232
+ storage: createFileStorage(CLIENT_AUTH_PATH)
233
+ }
234
+ });
235
+ }
236
+
237
+ function openBrowser(url) {
238
+ if (os.platform() === 'win32') {
239
+ spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
240
+ return;
241
+ }
242
+ const command = os.platform() === 'darwin' ? 'open' : 'xdg-open';
243
+ spawn(command, [url], { detached: true, stdio: 'ignore' }).unref();
244
+ }
245
+
246
+ async function startOAuthCallbackServer() {
247
+ let settleCode;
248
+ let rejectCode;
249
+ const waitForCode = new Promise((resolve, reject) => {
250
+ settleCode = resolve;
251
+ rejectCode = reject;
252
+ });
253
+ const server = createServer((req, res) => {
254
+ const requestUrl = new URL(req.url || '/', `http://127.0.0.1:${server.address().port}`);
255
+ const code = requestUrl.searchParams.get('code');
256
+ const error = requestUrl.searchParams.get('error_description') || requestUrl.searchParams.get('error');
257
+ if (error) {
258
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
259
+ res.end('<h1>LiveDesk sign-in failed</h1><p>You can close this tab.</p>');
260
+ rejectCode(new Error(error));
261
+ server.close();
262
+ return;
263
+ }
264
+ if (!code) {
265
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
266
+ res.end('LiveDesk sign-in callback is waiting for an auth code.');
267
+ return;
268
+ }
269
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
270
+ res.end('<h1>LiveDesk sign-in complete</h1><p>You can close this tab and return to the terminal.</p>');
271
+ settleCode(code);
272
+ server.close();
273
+ });
274
+ await new Promise((resolve, reject) => {
275
+ server.once('error', reject);
276
+ server.listen(0, '127.0.0.1', resolve);
277
+ });
278
+ return {
279
+ get redirectTo() {
280
+ return `http://127.0.0.1:${server.address().port}/callback`;
281
+ },
282
+ waitForCode
283
+ };
284
+ }
285
+
286
+ async function signInWithGoogle(supabase) {
287
+ const { data: existing } = await supabase.auth.getSession();
288
+ if (existing?.session?.access_token) {
289
+ return existing.session;
290
+ }
291
+
292
+ const callback = await startOAuthCallbackServer();
293
+ const { data, error } = await supabase.auth.signInWithOAuth({
294
+ provider: 'google',
295
+ options: {
296
+ redirectTo: callback.redirectTo,
297
+ scopes: 'email profile'
298
+ }
299
+ });
300
+ if (error) {
301
+ throw error;
302
+ }
303
+ if (!data?.url) {
304
+ throw new Error('Supabase did not return a Google sign-in URL.');
305
+ }
306
+
307
+ console.log('Opening Google sign-in for LiveDesk...');
308
+ openBrowser(data.url);
309
+ const code = await callback.waitForCode;
310
+ const { data: sessionData, error: exchangeError } = await supabase.auth.exchangeCodeForSession(code);
311
+ if (exchangeError) {
312
+ throw exchangeError;
313
+ }
314
+ if (!sessionData?.session) {
315
+ throw new Error('Google sign-in did not return a LiveDesk session.');
316
+ }
317
+ return sessionData.session;
318
+ }
319
+
320
+ function parseManagerEndpoint(value) {
321
+ let text = String(value || '').trim().replace(/^tcp:\/\//i, '');
322
+ const separator = text.lastIndexOf(':');
323
+ if (separator <= 0) {
324
+ return null;
325
+ }
326
+ const host = text.slice(0, separator).replace(/^\[/, '').replace(/\]$/, '').trim();
327
+ const port = Number(text.slice(separator + 1));
328
+ if (!host || !Number.isFinite(port)) {
329
+ return null;
330
+ }
331
+ return { host, port };
332
+ }
333
+
334
+ function canConnectToEndpoint(endpoint, timeoutMs = 1200) {
335
+ const parsedEndpoint = parseManagerEndpoint(endpoint);
336
+ if (!parsedEndpoint) {
337
+ return Promise.resolve(false);
338
+ }
339
+ return new Promise(resolve => {
340
+ const socket = net.createConnection(parsedEndpoint);
341
+ const finish = ok => {
342
+ socket.removeAllListeners();
343
+ socket.destroy();
344
+ resolve(ok);
345
+ };
346
+ socket.setTimeout(timeoutMs);
347
+ socket.once('connect', () => finish(true));
348
+ socket.once('timeout', () => finish(false));
349
+ socket.once('error', () => finish(false));
350
+ });
351
+ }
352
+
353
+ async function chooseReachableEndpoint(candidates) {
354
+ for (const endpoint of candidates) {
355
+ if (await canConnectToEndpoint(endpoint)) {
356
+ return endpoint;
357
+ }
358
+ }
359
+ return candidates[0] || DEFAULT_MANAGER;
360
+ }
361
+
362
+ function normalizeEndpointCandidates(target) {
363
+ const rawCandidates = [
364
+ target?.endpoint,
365
+ ...(Array.isArray(target?.endpoint_candidates) ? target.endpoint_candidates : [])
366
+ ];
367
+ return [...new Set(rawCandidates.map(value => String(value || '').trim()).filter(Boolean))];
368
+ }
369
+
370
+ async function resolveManagerFromSupabase(supabase) {
371
+ const { data, error } = await supabase
372
+ .from('livedesk_remote_host_targets')
373
+ .select('endpoint, endpoint_candidates, pair_token, active, expires_at, updated_at, manager_version')
374
+ .eq('product_key', 'livedesk')
375
+ .maybeSingle();
376
+ if (error) {
377
+ throw error;
378
+ }
379
+ if (!data?.active) {
380
+ throw new Error('No active LiveDesk manager was found for this Google account. Start LiveDesk on the manager computer and sign in there first.');
381
+ }
382
+ if (data.expires_at && Date.parse(data.expires_at) <= Date.now()) {
383
+ throw new Error('The LiveDesk manager record is expired. Open the manager screen again while signed in.');
384
+ }
385
+ if (!data.pair_token) {
386
+ throw new Error('The LiveDesk manager record is missing its private connection token.');
387
+ }
388
+ const endpointCandidates = normalizeEndpointCandidates(data);
389
+ if (endpointCandidates.length === 0) {
390
+ throw new Error('The LiveDesk manager record does not contain a reachable local address.');
391
+ }
392
+ const manager = await chooseReachableEndpoint(endpointCandidates);
393
+ return {
394
+ manager,
395
+ pair: data.pair_token,
396
+ endpointCandidates
397
+ };
398
+ }
399
+
400
+ async function promptForSlotNumber(currentSlot) {
401
+ const normalized = normalizeSlotNumber(currentSlot);
402
+ if (normalized || !process.stdin.isTTY || !process.stdout.isTTY) {
403
+ return normalized;
404
+ }
405
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
406
+ try {
407
+ const answer = await rl.question('Computer number for screen wall (1-99, Enter to skip): ');
408
+ return normalizeSlotNumber(answer);
409
+ } finally {
410
+ rl.close();
411
+ }
412
+ }
413
+
414
+ async function prepareLoginConnection(parsed) {
415
+ if (parsed.logout) {
416
+ rmSync(CLIENT_AUTH_PATH, { force: true });
417
+ }
418
+ const shouldLogin = !parsed.loginDisabled
419
+ && parsed.command === 'connect'
420
+ && (parsed.loginRequested || !parsed.pair);
421
+ let forwarded = [...parsed.forwarded];
422
+ let manager = parsed.manager;
423
+ let pair = parsed.pair;
424
+
425
+ if (shouldLogin) {
426
+ const supabase = await createSupabaseClient();
427
+ const session = await signInWithGoogle(supabase);
428
+ const email = session.user?.email ? ` as ${session.user.email}` : '';
429
+ console.log(`Signed in to LiveDesk${email}.`);
430
+ const resolved = await resolveManagerFromSupabase(supabase);
431
+ manager = resolved.manager;
432
+ pair = resolved.pair;
433
+ console.log(`Found LiveDesk manager at ${manager}.`);
434
+ }
435
+
436
+ const slot = await promptForSlotNumber(parsed.slot);
437
+ if (manager) {
438
+ forwarded = upsertForwardedOption(forwarded, '--manager', manager);
439
+ }
440
+ if (pair) {
441
+ forwarded = upsertForwardedOption(forwarded, '--pair', pair);
442
+ }
443
+ if (slot) {
444
+ forwarded = upsertForwardedOption(forwarded, '--slot', slot);
445
+ }
446
+ return {
447
+ ...parsed,
448
+ manager,
449
+ pair,
450
+ slot,
451
+ forwarded
452
+ };
453
+ }
454
+
89
455
  function getFastRuntime() {
90
456
  const platform = os.platform();
91
457
  const arch = os.arch();
@@ -281,26 +647,34 @@ function shouldUseFast(parsed) {
281
647
  return !parsed.nodeOnlyFeature;
282
648
  }
283
649
 
284
- const parsed = parseLauncherArgs(process.argv.slice(2));
285
- if (parsed.help) {
286
- printHelp();
287
- process.exit(0);
288
- }
650
+ async function main() {
651
+ const parsed = parseLauncherArgs(process.argv.slice(2));
652
+ if (parsed.help) {
653
+ printHelp();
654
+ return;
655
+ }
289
656
 
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);
657
+ const prepared = await prepareLoginConnection(parsed);
658
+ const fastRuntime = getFastRuntime();
659
+ const useFast = shouldUseFast(prepared);
660
+ if (useFast) {
661
+ const fastArgs = buildFastArgs(prepared.forwarded, prepared.fakeThumbnail);
662
+ const fastLaunch = resolveFastLaunch(fastRuntime);
663
+ if (fastLaunch.ok) {
664
+ spawnAgent(fastLaunch.command, [...fastLaunch.argsPrefix, ...fastArgs]);
665
+ } else if (prepared.engine === 'fast') {
666
+ console.error(`C# RemoteFast is unavailable: ${fastLaunch.reason}. Use --engine node to run the legacy Node agent.`);
667
+ process.exit(2);
668
+ } else {
669
+ console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
670
+ spawnAgent(process.execPath, [nodeAgentPath, ...prepared.forwarded]);
671
+ }
300
672
  } else {
301
- console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
302
- spawnAgent(process.execPath, [nodeAgentPath, ...parsed.forwarded]);
673
+ spawnAgent(process.execPath, [nodeAgentPath, ...prepared.forwarded]);
303
674
  }
304
- } else {
305
- spawnAgent(process.execPath, [nodeAgentPath, ...parsed.forwarded]);
306
675
  }
676
+
677
+ main().catch(error => {
678
+ console.error(error?.message || error);
679
+ process.exit(1);
680
+ });
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
7
+ "client": "bin/livedesk-client.js",
7
8
  "livedesk-client": "bin/livedesk-client.js",
8
9
  "livedesk-client-node": "bin/livedesk-client-node.js",
9
10
  "livedesk-client-fast": "bin/livedesk-client-fast.js"
@@ -29,6 +30,7 @@
29
30
  "node": ">=20"
30
31
  },
31
32
  "dependencies": {
33
+ "@supabase/supabase-js": "^2.110.0",
32
34
  "node-screenshots": "^0.2.8",
33
35
  "openai": "^6.42.0"
34
36
  },