@livedesk/client 0.1.250 → 0.1.252

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
@@ -1,6 +1,6 @@
1
- # LiveDesk Client
1
+ # VuvoDesk Client
2
2
 
3
- For normal users, run the unified LiveDesk application:
3
+ For normal users, run the unified VuvoDesk application:
4
4
 
5
5
  ```powershell
6
6
  npx -y --prefer-online livedesk@latest
@@ -20,7 +20,7 @@ aligned.
20
20
  The default flow lets the local Client runtime own Google authorization,
21
21
  callback exchange, and saved authentication as one path. The browser only
22
22
  shows progress; it does not maintain a second session that must be transferred
23
- back to the Client. LiveDesk then waits for the active Hub published by the
23
+ back to the Client. VuvoDesk then waits for the active Hub published by the
24
24
  same account and connects locally. If the Hub is not
25
25
  ready yet, the client waits for a Hub-online event and polls the registry at
26
26
  most every six seconds as a fallback. Missing a wake event can never leave a
@@ -34,10 +34,10 @@ On Windows, check **Start with Windows** on the connection page to reconnect thi
34
34
  client automatically after reboot. The startup entry reuses the saved Google
35
35
  session and only opens the connection page again if sign-in is needed.
36
36
 
37
- On the Hub computer, open the LiveDesk dashboard, sign in, and run **Sync
37
+ On the Hub computer, open the VuvoDesk dashboard, sign in, and run **Sync
38
38
  Server** once. The dashboard transfers the signed-in session to the local Hub;
39
39
  the Hub process then keeps its own local address and private pair token
40
- refreshed in the LiveDesk Supabase registry. The dashboard can be closed after
40
+ refreshed in the VuvoDesk Supabase registry. The dashboard can be closed after
41
41
  that while the Hub remains running.
42
42
 
43
43
  Supabase Auth must allow the CLI callback URL:
@@ -55,12 +55,12 @@ can receive safe task-only instructions. The C# RemoteFast engine also supports
55
55
  keyboard/mouse control on Windows, macOS, and Linux/X11, file transfer, and
56
56
  system-audio playback from Windows, macOS, and Linux. Linux captures the default
57
57
  output through PipeWire `pw-record` or PulseAudio `parec`. macOS control requires Accessibility
58
- permission for the terminal app that started LiveDesk; screen streaming and
59
- system-audio capture require Screen Recording permission. LiveDesk checks these
58
+ permission for the terminal app that started VuvoDesk; screen streaming and
59
+ system-audio capture require Screen Recording permission. VuvoDesk checks these
60
60
  permissions without opening repeated system dialogs and prints the matching
61
61
  System Settings path when access is missing; after granting access, restart the
62
62
  client. When file transfer is enabled by the Hub, received files are saved
63
- to `Desktop/LiveDeskFiles` unless the Hub sets another destination folder.
63
+ to `Desktop/VuvoDeskFiles` unless the Hub sets another destination folder.
64
64
 
65
65
  By default, the launcher uses the packaged C# RemoteFast engine when supported.
66
66
  It falls back to the Node engine only when a compatible RemoteFast runtime is
@@ -80,7 +80,7 @@ npx -y --prefer-online livedesk@latest client 3
80
80
  npx -y --prefer-online livedesk@latest client --no-thumbnail
81
81
  npx -y --prefer-online livedesk@latest client --no-live
82
82
  npx -y --prefer-online livedesk@latest client --no-audio
83
- npx -y --prefer-online livedesk@latest client --files-dir "D:\LiveDeskFiles"
83
+ npx -y --prefer-online livedesk@latest client --files-dir "D:\VuvoDeskFiles"
84
84
  npx -y --prefer-online livedesk@latest client --engine node
85
85
  npx -y --prefer-online livedesk@latest client --engine fast --trace-frames
86
86
  npx -y --prefer-online livedesk@latest client --auth-port 5200
@@ -91,7 +91,7 @@ Frame pipeline roadmap:
91
91
 
92
92
  - Mode 1: `mode1-jpeg` - current test path using screen capture, resize, and JPEG binary frames.
93
93
  - Mode 2: `mode2-lzo` - cross-platform wall path using independent RGB565LE frames capped at 320x180 and compressed as LZO1X blocks. The wall defaults to 8 fps. macOS and Linux keep one persistent local capture helper per client process. Legacy `mode2-lz4` settings migrate to this mode.
94
- - Mode 3: `mode3-h264-hw` - OS-specific H.264 path. Windows tries Media Foundation/NVENC/QSV/AMF, macOS prefers ScreenCaptureKit plus VideoToolbox, and Linux tries NVENC/VAAPI/QSV before falling back to the bundled ffmpeg `libx264` encoder. Linux users do not need to install ffmpeg separately when starting LiveDesk through the published npm package. macOS emits one startup key frame and then an approximately half-second recovery GOP. The launcher uses bundled ffmpeg for fallback paths unless `LIVEDESK_FFMPEG` points to a custom binary. On macOS, set `LIVEDESK_FFMPEG_AVFOUNDATION_INPUT` only when the ScreenCaptureKit helper is unavailable and the AVFoundation fallback input is not `1:none`.
94
+ - Mode 3: `mode3-h264-hw` - OS-specific H.264 path. Windows tries Media Foundation/NVENC/QSV/AMF, macOS prefers ScreenCaptureKit plus VideoToolbox, and Linux tries NVENC/VAAPI/QSV before falling back to the bundled ffmpeg `libx264` encoder. Linux users do not need to install ffmpeg separately when starting VuvoDesk through the published npm package. macOS emits one startup key frame and then an approximately half-second recovery GOP. The launcher uses bundled ffmpeg for fallback paths unless `LIVEDESK_FFMPEG` points to a custom binary. On macOS, set `LIVEDESK_FFMPEG_AVFOUNDATION_INPUT` only when the ScreenCaptureKit helper is unavailable and the AVFoundation fallback input is not `1:none`.
95
95
 
96
96
  Legacy mode names such as `remote-fast` and `remote-quality` are treated as
97
97
  Mode 1 aliases.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## lzokay
4
4
 
5
- LiveDesk's LZO1X block encoder and decoder follow the instruction layout of
5
+ VuvoDesk's LZO1X block encoder and decoder follow the instruction layout of
6
6
  [lzokay](https://github.com/AxioDL/lzokay), Copyright (c) 2018 Jack Andersen.
7
7
 
8
8
  The MIT License
@@ -19,5 +19,5 @@ export function formatClientVersionBanner(env = process.env) {
19
19
  const launcherVersion = String(env.LIVEDESK_NPM_LAUNCHER_VERSION || '').trim();
20
20
  const productName = launcherName || 'livedesk';
21
21
  const productVersion = launcherVersion || readClientPackageVersion();
22
- return `[LiveDesk Client] ${productName}@${productVersion}`;
22
+ return `[VuvoDesk Client] ${productName}@${productVersion}`;
23
23
  }
@@ -8,6 +8,7 @@ import { existsSync, promises as fs, statfsSync } from 'fs';
8
8
  import { spawn } from 'child_process';
9
9
  import { createRequire } from 'node:module';
10
10
  import { fileURLToPath } from 'node:url';
11
+ import { resolveAgentShellCommand } from '../src/runtime/agent-shell.js';
11
12
 
12
13
  const require = createRequire(import.meta.url);
13
14
  const CLIENT_UPDATE_BOOTSTRAP_PATH = fileURLToPath(
@@ -39,7 +40,7 @@ function normalizeClientUpdateTargetVersion(value) {
39
40
  const version = String(value || '').trim().replace(/^v/i, '');
40
41
  if (!version) return '';
41
42
  if (!CLIENT_UPDATE_VERSION_PATTERN.test(version)) {
42
- throw new Error(`Invalid LiveDesk product update version: ${version}`);
43
+ throw new Error(`Invalid VuvoDesk product update version: ${version}`);
43
44
  }
44
45
  return version;
45
46
  }
@@ -63,7 +64,7 @@ async function prepareClientUpdateNeutralCwd(operationId) {
63
64
  const unexpectedEntry = (await fs.readdir(neutralCwd))[0];
64
65
  if (unexpectedEntry) {
65
66
  const shadowPath = path.join(neutralCwd, unexpectedEntry);
66
- throw new Error(`LiveDesk update neutral working directory is shadowed by ${shadowPath}: ${neutralCwd}`);
67
+ throw new Error(`VuvoDesk update neutral working directory is shadowed by ${shadowPath}: ${neutralCwd}`);
67
68
  }
68
69
  return neutralCwd;
69
70
  }
@@ -227,8 +228,8 @@ async function waitForClientUpdateShutdownSignal(
227
228
  return { ready: true, error: '' };
228
229
  }
229
230
  if (state.stage === 'failed') {
230
- const error = String(state.error || 'LiveDesk client update preparation failed.');
231
- console.error(`LiveDesk client update preparation failed: ${error}`);
231
+ const error = String(state.error || 'VuvoDesk client update preparation failed.');
232
+ console.error(`VuvoDesk client update preparation failed: ${error}`);
232
233
  return { ready: false, error };
233
234
  }
234
235
  } catch {
@@ -238,8 +239,8 @@ async function waitForClientUpdateShutdownSignal(
238
239
  }
239
240
  const deadlineExpired = Date.now() >= Number(updateDeadlineEpochMs);
240
241
  const error = deadlineExpired
241
- ? 'The absolute LiveDesk Client update deadline expired before shutdown.'
242
- : 'Timed out waiting for the LiveDesk update supervisor to prepare the exact package.';
242
+ ? 'The absolute VuvoDesk Client update deadline expired before shutdown.'
243
+ : 'Timed out waiting for the VuvoDesk update supervisor to prepare the exact package.';
243
244
  await writeClientUpdateState('failed', {
244
245
  operationId,
245
246
  error,
@@ -247,7 +248,7 @@ async function waitForClientUpdateShutdownSignal(
247
248
  deadlineExpired,
248
249
  failedAt: new Date().toISOString()
249
250
  }).catch(() => undefined);
250
- console.error('LiveDesk client update preparation timed out; the current Agent will remain running.');
251
+ console.error('VuvoDesk client update preparation timed out; the current Agent will remain running.');
251
252
  return { ready: false, error };
252
253
  }
253
254
 
@@ -261,7 +262,7 @@ function markClientUpdateConnected() {
261
262
  restartVerified: true,
262
263
  error: ''
263
264
  }).catch(error => {
264
- console.error(`LiveDesk client update diagnostics could not be persisted: ${error?.message || error}`);
265
+ console.error(`VuvoDesk client update diagnostics could not be persisted: ${error?.message || error}`);
265
266
  });
266
267
  }
267
268
 
@@ -318,14 +319,14 @@ async function publishClientUpdateSupervisorProof(socket) {
318
319
 
319
320
  function printHelp() {
320
321
  console.log(`
321
- LiveDesk Client
322
+ VuvoDesk Client
322
323
 
323
324
  Usage:
324
325
  npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token>
325
326
 
326
327
  Options:
327
- --manager <host:port> LiveDesk Hub address. Default: ${DEFAULT_MANAGER}
328
- --pair <token> LiveDesk Hub pairing token. Can also use LIVEDESK_CLIENT_PAIR_TOKEN.
328
+ --manager <host:port> VuvoDesk Hub address. Default: ${DEFAULT_MANAGER}
329
+ --pair <token> VuvoDesk Hub pairing token. Can also use LIVEDESK_CLIENT_PAIR_TOKEN.
329
330
  --slot <number> Screen wall slot number for this computer.
330
331
  --name <name> Friendly device name. Default: OS hostname.
331
332
  --heartbeat <ms> Status heartbeat interval. Default: ${DEFAULT_HEARTBEAT_MS}
@@ -336,7 +337,7 @@ Options:
336
337
  --no-live Disable focused live screen streaming.
337
338
  --tasks Enable safe remote task inbox. Default on.
338
339
  --no-tasks Disable remote task dispatch capability.
339
- --files-dir <path> Default folder for received files. Default: ~/Desktop/LiveDeskFiles.
340
+ --files-dir <path> Default folder for received files. Default: ~/Desktop/VuvoDeskFiles.
340
341
  --fake-thumbnail Use generated thumbnail frames for smoke tests.
341
342
  --exit-on-disconnect Exit after Hub disconnect. Useful for tests.
342
343
  --exit-on-invalid-pair Exit when the Hub rejects the pair token.
@@ -684,7 +685,7 @@ async function waitForNodeServiceState(executable, service, expectedState, timeo
684
685
  }
685
686
 
686
687
  function getDefaultFilesDir() {
687
- return path.join(os.homedir(), 'Desktop', 'LiveDeskFiles');
688
+ return path.join(os.homedir(), 'Desktop', 'VuvoDeskFiles');
688
689
  }
689
690
 
690
691
  function normalizeDirectoryPath(value, fallback = getDefaultFilesDir()) {
@@ -861,7 +862,7 @@ function createFakeThumbnailFrame(options, payload, frameSeq) {
861
862
  '<rect width="100%" height="100%" fill="#0f172a"/>',
862
863
  '<rect x="14" y="14" width="calc(100% - 28px)" height="calc(100% - 28px)" rx="12" fill="#1e293b" stroke="#38bdf8" stroke-width="2"/>',
863
864
  `<text x="28" y="48" fill="#e2e8f0" font-family="Arial, sans-serif" font-size="20" font-weight="700">${title}</text>`,
864
- `<text x="28" y="82" fill="#93c5fd" font-family="Arial, sans-serif" font-size="14">LiveDesk Client Thumbnail</text>`,
865
+ `<text x="28" y="82" fill="#93c5fd" font-family="Arial, sans-serif" font-size="14">VuvoDesk Client Thumbnail</text>`,
865
866
  `<text x="28" y="${height - 30}" fill="#94a3b8" font-family="Consolas, monospace" font-size="12">${now}</text>`,
866
867
  '</svg>'
867
868
  ].join('');
@@ -1118,7 +1119,7 @@ async function assertAgentPathDoesNotTraverseLink(base, resolved) {
1118
1119
  }
1119
1120
  const canonicalCurrent = await fs.realpath(current);
1120
1121
  if (!isPathWithinAgentRoot(canonicalRoot, canonicalCurrent)) {
1121
- throw new Error('Safe Agent path resolves outside the LiveDesk files directory.');
1122
+ throw new Error('Safe Agent path resolves outside the VuvoDesk files directory.');
1122
1123
  }
1123
1124
  return;
1124
1125
  } catch (error) {
@@ -1136,14 +1137,15 @@ async function resolveAgentPath(options, value, permissionMode, rejectSensitive
1136
1137
  if (!text || text.length > 600) throw new Error('path is invalid');
1137
1138
  const resolved = path.resolve(path.isAbsolute(text) ? text : path.join(base, text));
1138
1139
  const relative = path.relative(base, resolved);
1139
- if (permissionMode !== 'full-access' && (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))) throw new Error('path is outside the LiveDesk files directory for this permission mode');
1140
+ if (permissionMode !== 'full-access' && (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))) throw new Error('path is outside the VuvoDesk files directory for this permission mode');
1140
1141
  if (permissionMode !== 'full-access') await assertAgentPathDoesNotTraverseLink(base, resolved);
1141
1142
  if (rejectSensitive && isSensitiveAgentPath(resolved)) throw new Error('credential and secret paths are not available');
1142
1143
  return resolved;
1143
1144
  }
1144
1145
 
1145
- function runNodeAgentProcess(executable, args, timeoutMs = 15000, cwd = undefined) {
1146
+ function runNodeAgentProcess(executable, args, timeoutMs = 15000, cwd = undefined, maxTimeoutMs = 30000) {
1146
1147
  return new Promise((resolve, reject) => {
1148
+ const startedAt = Date.now();
1147
1149
  const child = spawn(executable, args, { cwd, windowsHide: true, shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
1148
1150
  let output = '';
1149
1151
  let timedOut = false;
@@ -1154,12 +1156,56 @@ function runNodeAgentProcess(executable, args, timeoutMs = 15000, cwd = undefine
1154
1156
  timedOut = true;
1155
1157
  child.kill('SIGTERM');
1156
1158
  setTimeout(() => child.kill('SIGKILL'), 1000).unref?.();
1157
- }, Math.max(1000, Math.min(30000, Number(timeoutMs) || 15000)));
1159
+ }, Math.max(1000, Math.min(maxTimeoutMs, Number(timeoutMs) || 15000)));
1158
1160
  child.once('error', error => { clearTimeout(timer); reject(error); });
1159
- child.once('close', code => { clearTimeout(timer); resolve({ output: redactAgentOutput(`${output}${timedOut ? '\n[timeout]' : ''}`.trim()), exitCode: timedOut ? -1 : code ?? -1, timedOut }); });
1161
+ child.once('close', code => { clearTimeout(timer); resolve({ output: redactAgentOutput(`${output}${timedOut ? '\n[timeout]' : ''}`.trim()), exitCode: timedOut ? -1 : code ?? -1, timedOut, durationMs: Date.now() - startedAt }); });
1160
1162
  });
1161
1163
  }
1162
1164
 
1165
+ async function searchNodeAgentFiles(options, args, permissionMode) {
1166
+ const root = await resolveAgentPath(options, args.path, permissionMode);
1167
+ const query = String(args.query || '').trim().toLowerCase();
1168
+ if (!query || query.length > 160 || /[\0\r\n]/.test(query)) throw new Error('search query is invalid');
1169
+ const maxResults = Math.max(1, Math.min(200, Number(args.maxResults) || 100));
1170
+ const maxDepth = Math.max(0, Math.min(8, Number(args.maxDepth) || 4));
1171
+ const maxScannedEntries = Math.max(maxResults, Math.min(5000, Number(args.maxScannedEntries) || 2000));
1172
+ const queue = [{ directory: root, depth: 0 }];
1173
+ const matches = [];
1174
+ let scannedEntries = 0;
1175
+ while (queue.length > 0 && matches.length < maxResults && scannedEntries < maxScannedEntries) {
1176
+ const current = queue.shift();
1177
+ let entries;
1178
+ try {
1179
+ entries = await fs.readdir(current.directory, { withFileTypes: true });
1180
+ } catch {
1181
+ continue;
1182
+ }
1183
+ for (const entry of entries) {
1184
+ if (matches.length >= maxResults || scannedEntries >= maxScannedEntries) break;
1185
+ scannedEntries += 1;
1186
+ const entryPath = path.join(current.directory, entry.name);
1187
+ if (isSensitiveAgentPath(entryPath)) continue;
1188
+ const isDirectory = entry.isDirectory();
1189
+ if (entry.name.toLowerCase().includes(query)) {
1190
+ matches.push({ name: entry.name, path: entryPath, type: isDirectory ? 'directory' : 'file' });
1191
+ }
1192
+ if (isDirectory && current.depth < maxDepth && !entry.isSymbolicLink()) {
1193
+ queue.push({ directory: entryPath, depth: current.depth + 1 });
1194
+ }
1195
+ }
1196
+ }
1197
+ return {
1198
+ summary: `Found ${matches.length} matching path${matches.length === 1 ? '' : 's'}.`,
1199
+ data: {
1200
+ path: root,
1201
+ query,
1202
+ matches,
1203
+ scannedEntries,
1204
+ truncated: matches.length >= maxResults || scannedEntries >= maxScannedEntries
1205
+ }
1206
+ };
1207
+ }
1208
+
1163
1209
  function redactAgentOutput(value) {
1164
1210
  let output = String(value || '').replace(/[\0\r]/g, ' ');
1165
1211
  output = output.replace(/\b(token|secret|password|api[-_]?key|authorization|private[-_]?key|connection[-_]?string|access[-_]?key|client[-_]?secret)\b\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/gi, '$1=[redacted]');
@@ -1284,7 +1330,7 @@ async function persistNodeEnvironmentVariable(name, value) {
1284
1330
  return { persisted: true, scope: 'user', output: result.output };
1285
1331
  }
1286
1332
  const profilePath = path.join(os.homedir(), '.profile');
1287
- const marker = `# LiveDesk managed environment: ${variableName}`;
1333
+ const marker = `# VuvoDesk managed environment: ${variableName}`;
1288
1334
  const quoted = `'${variableValue.replaceAll("'", "'\\\"'\\\"'")}'`;
1289
1335
  let profile = '';
1290
1336
  try { profile = await fs.readFile(profilePath, 'utf8'); } catch { /* create on demand */ }
@@ -1336,6 +1382,13 @@ async function executeNodeAgentOperation(options, operation, payload = {}) {
1336
1382
  }
1337
1383
  return { summary: `Listed ${data.length} entries.`, data: { path: directory, entries: data, truncated: entries.length > maxEntries } };
1338
1384
  }
1385
+ if (operation === 'file.search') return searchNodeAgentFiles(options, args, permissionMode);
1386
+ if (operation === 'directory.create') {
1387
+ let directory = await resolveAgentPath(options, args.path, permissionMode);
1388
+ await fs.mkdir(directory, { recursive: true });
1389
+ directory = await resolveAgentPath(options, args.path, permissionMode);
1390
+ return { summary: `Created directory ${path.basename(directory)}.`, data: { path: directory, created: true } };
1391
+ }
1339
1392
  if (operation === 'application.launch') {
1340
1393
  const executable = String(args.executable || '').trim();
1341
1394
  if (!executable || executable.length > 400 || /[\0\r\n]/.test(executable)) throw new Error('executable is invalid');
@@ -1391,10 +1444,11 @@ async function executeNodeAgentOperation(options, operation, payload = {}) {
1391
1444
  }
1392
1445
  if (operation === 'command.run') {
1393
1446
  const command = String(args.command || '');
1394
- const executable = process.platform === 'win32' ? 'cmd.exe' : '/bin/sh';
1395
- const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', command] : ['-lc', command];
1396
- const result = await runNodeAgentProcess(executable, commandArgs, args.timeoutMs, args.workingDirectory ? await resolveAgentPath(options, args.workingDirectory, permissionMode, false) : undefined);
1397
- return { summary: result.timedOut ? 'Command timed out.' : `Command exited with code ${result.exitCode}.`, data: { ...result, output: redactAgentOutput(result.output) } };
1447
+ if (!command || command.length > 16000 || /\0/.test(command)) throw new Error('command is invalid');
1448
+ const shellCommand = resolveAgentShellCommand({ shell: args.shell, command });
1449
+ const workingDirectory = args.workingDirectory ? await resolveAgentPath(options, args.workingDirectory, permissionMode, false) : undefined;
1450
+ const result = await runNodeAgentProcess(shellCommand.executable, shellCommand.args, args.timeoutMs, workingDirectory, 300000);
1451
+ return { summary: result.timedOut ? `${shellCommand.shell} command timed out.` : `${shellCommand.shell} command exited with code ${result.exitCode}.`, data: { ...result, output: redactAgentOutput(result.output), shell: shellCommand.shell, workingDirectory: workingDirectory || process.cwd() } };
1398
1452
  }
1399
1453
  if (operation === 'script.run') {
1400
1454
  const scriptPath = await resolveAgentPath(options, args.path, permissionMode);
@@ -1481,7 +1535,7 @@ function normalizeNodeAgentTaskResult(result) {
1481
1535
  return { ...result, ok, status: ok ? 'completed' : 'failed', error };
1482
1536
  }
1483
1537
 
1484
- const NODE_AGENT_OPERATIONS = new Set(['process.control', 'service.control', 'application.launch', 'application.close', 'file.read', 'file.write', 'file.delete', 'file.list', 'command.run', 'script.run', 'software.install', 'network.status', 'system.power', 'system.configure', 'logs.collect']);
1538
+ const NODE_AGENT_OPERATIONS = new Set(['process.control', 'service.control', 'application.launch', 'application.close', 'file.read', 'file.write', 'file.delete', 'file.list', 'file.search', 'directory.create', 'command.run', 'script.run', 'software.install', 'network.status', 'system.power', 'system.configure', 'logs.collect']);
1485
1539
 
1486
1540
  function remotePolicyAllows(options, command) {
1487
1541
  const policy = options.effectivePolicy;
@@ -1533,7 +1587,7 @@ async function scheduleClientUpdate(options, payload = {}) {
1533
1587
  if (!Number.isSafeInteger(updateTimeoutMs)
1534
1588
  || updateTimeoutMs < 1_000
1535
1589
  || updateTimeoutMs > 90 * 60_000) {
1536
- throw new Error('LiveDesk Client update requires a bounded deadline budget.');
1590
+ throw new Error('VuvoDesk Client update requires a bounded deadline budget.');
1537
1591
  }
1538
1592
  updateDeadlineEpochMs = Date.now() + updateTimeoutMs;
1539
1593
  const originalCwd = path.resolve(process.cwd());
@@ -1554,7 +1608,7 @@ async function scheduleClientUpdate(options, payload = {}) {
1554
1608
  ? configuredParentPid
1555
1609
  : process.pid;
1556
1610
  if (!Number.isInteger(parentPid) || parentPid <= 1) {
1557
- throw new Error('LiveDesk client launcher process id is unavailable.');
1611
+ throw new Error('VuvoDesk client launcher process id is unavailable.');
1558
1612
  }
1559
1613
  const nodeBinDir = path.dirname(process.execPath);
1560
1614
  const npxCandidates = process.platform === 'win32'
@@ -1562,7 +1616,7 @@ async function scheduleClientUpdate(options, payload = {}) {
1562
1616
  : [process.env.LIVEDESK_NPX_EXECUTABLE, path.join(nodeBinDir, 'npx'), 'npx'];
1563
1617
  const command = npxCandidates.find(candidate => candidate && (!path.isAbsolute(candidate) || existsSync(candidate)));
1564
1618
  if (!command) {
1565
- throw new Error('LiveDesk npx executable was not found.');
1619
+ throw new Error('VuvoDesk npx executable was not found.');
1566
1620
  }
1567
1621
  const frozenCommand = path.isAbsolute(command) || !/[\\/]/.test(command)
1568
1622
  ? command
@@ -1597,7 +1651,7 @@ async function scheduleClientUpdate(options, payload = {}) {
1597
1651
  }, neutralCwd);
1598
1652
  const bootstrapStat = await fs.stat(CLIENT_UPDATE_BOOTSTRAP_PATH);
1599
1653
  if (!bootstrapStat.isFile() || bootstrapStat.size <= 0) {
1600
- throw new Error(`LiveDesk Client update bootstrap is unavailable: ${CLIENT_UPDATE_BOOTSTRAP_PATH}`);
1654
+ throw new Error(`VuvoDesk Client update bootstrap is unavailable: ${CLIENT_UPDATE_BOOTSTRAP_PATH}`);
1601
1655
  }
1602
1656
  return await new Promise((resolve, reject) => {
1603
1657
  const child = spawn(process.execPath, [CLIENT_UPDATE_BOOTSTRAP_PATH], {
@@ -1723,7 +1777,7 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
1723
1777
  summary: result.summary,
1724
1778
  error: result.error || undefined,
1725
1779
  data: result.data,
1726
- sideEffects: ['file.read', 'file.list', 'network.status', 'logs.collect'].includes(command) ? 'none' : 'audited',
1780
+ sideEffects: ['file.read', 'file.list', 'file.search', 'network.status', 'logs.collect'].includes(command) ? 'none' : 'audited',
1727
1781
  completedAt: new Date().toISOString()
1728
1782
  }
1729
1783
  });
@@ -1954,7 +2008,7 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
1954
2008
  function connectOnce(options, deviceId) {
1955
2009
  const manager = parseManagerAddress(options.manager);
1956
2010
  if (!isPrivateLanHost(manager.host) && !isTruthy(process.env.LIVEDESK_ALLOW_UNENCRYPTED_LAN)) {
1957
- return Promise.reject(new Error('Plain TCP LiveDesk connections are limited to loopback/private LAN. Use an encrypted endpoint for external connections.'));
2011
+ return Promise.reject(new Error('Plain TCP VuvoDesk connections are limited to loopback/private LAN. Use an encrypted endpoint for external connections.'));
1958
2012
  }
1959
2013
  return new Promise((resolve, reject) => {
1960
2014
  const socket = net.createConnection({
@@ -2052,11 +2106,11 @@ function connectOnce(options, deviceId) {
2052
2106
  options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
2053
2107
  ? message.effectivePolicy
2054
2108
  : null;
2055
- console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
2109
+ console.log(`Connected to VuvoDesk Hub as ${options.name} (${deviceId})`);
2056
2110
  markClientUpdateConnected();
2057
2111
  void publishClientUpdateSupervisorProof(socket).catch(error => {
2058
2112
  if (!socket.destroyed) {
2059
- console.error(`LiveDesk client update verification proof failed: ${error?.message || error}`);
2113
+ console.error(`VuvoDesk client update verification proof failed: ${error?.message || error}`);
2060
2114
  }
2061
2115
  });
2062
2116
  writeJsonLine(socket, {
@@ -2093,7 +2147,7 @@ function connectOnce(options, deviceId) {
2093
2147
  finish();
2094
2148
  return;
2095
2149
  } else if (message.type === 'error') {
2096
- const remoteError = new Error(message.error || 'LiveDesk Hub rejected the connection.');
2150
+ const remoteError = new Error(message.error || 'VuvoDesk Hub rejected the connection.');
2097
2151
  if (message.error === 'invalid-pair-token' && options.exitOnInvalidPair) {
2098
2152
  remoteError.exitCode = EXIT_INVALID_PAIR_TOKEN;
2099
2153
  }
@@ -2110,7 +2164,7 @@ function connectOnce(options, deviceId) {
2110
2164
 
2111
2165
  async function connectWithRetry(options) {
2112
2166
  if (!options.pair) {
2113
- throw new Error('Missing --pair token. Sign in with Google or get it from LiveDesk Hub status.');
2167
+ throw new Error('Missing --pair token. Sign in with Google or get it from VuvoDesk Hub status.');
2114
2168
  }
2115
2169
 
2116
2170
  const deviceId = await getDeviceId(options.deviceId);
@@ -2119,7 +2173,7 @@ async function connectWithRetry(options) {
2119
2173
 
2120
2174
  process.once('SIGINT', () => {
2121
2175
  stopping = true;
2122
- console.log('\nStopping LiveDesk Client...');
2176
+ console.log('\nStopping VuvoDesk Client...');
2123
2177
  });
2124
2178
  process.once('SIGTERM', () => {
2125
2179
  stopping = true;
@@ -2138,7 +2192,7 @@ async function connectWithRetry(options) {
2138
2192
  throw err;
2139
2193
  }
2140
2194
  const delayMs = DEFAULT_RECONNECT_MS;
2141
- console.error(`LiveDesk Hub connection failed: ${err?.message || err}. Retrying in ${delayMs}ms.`);
2195
+ console.error(`VuvoDesk Hub connection failed: ${err?.message || err}. Retrying in ${delayMs}ms.`);
2142
2196
  await wait(delayMs);
2143
2197
  }
2144
2198
  }
@@ -79,7 +79,7 @@ function withStateLock(callback) {
79
79
  }
80
80
  if (error?.code !== 'EEXIST') throw error;
81
81
  if (removeAbandonedStateLock()) continue;
82
- if (Date.now() >= deadline) throw new Error(`Timed out waiting for the LiveDesk update state lock: ${stateLockPath}`);
82
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for the VuvoDesk update state lock: ${stateLockPath}`);
83
83
  Atomics.wait(lockWaitBuffer, 0, 0, Math.min(25, Math.max(1, deadline - Date.now())));
84
84
  }
85
85
  }
@@ -125,26 +125,26 @@ function writeFailure(error, cancelRequested = false) {
125
125
  };
126
126
  }, 'handoff-failed');
127
127
  } catch { /* preserve the original handoff failure */ }
128
- process.stderr.write(`LiveDesk update handoff failed: ${error?.message || error}\n`);
128
+ process.stderr.write(`VuvoDesk update handoff failed: ${error?.message || error}\n`);
129
129
  process.exitCode = 1;
130
130
  }
131
131
 
132
132
  function prepareNeutralCwd() {
133
- if (!neutralCwd) throw new Error('LiveDesk update neutral working directory is unavailable.');
133
+ if (!neutralCwd) throw new Error('VuvoDesk update neutral working directory is unavailable.');
134
134
  fs.mkdirSync(neutralCwd, { recursive: true });
135
135
  const unexpectedEntry = fs.readdirSync(neutralCwd)[0];
136
136
  if (unexpectedEntry) {
137
- throw new Error(`LiveDesk update neutral working directory is shadowed by ${path.join(neutralCwd, unexpectedEntry)}.`);
137
+ throw new Error(`VuvoDesk update neutral working directory is shadowed by ${path.join(neutralCwd, unexpectedEntry)}.`);
138
138
  }
139
139
  }
140
140
 
141
141
  function writeHandoffStarted() {
142
142
  return writeState(previous => {
143
143
  if (Date.now() >= updateDeadlineEpochMs) {
144
- throw new Error('The absolute LiveDesk Client update deadline expired before handoff.');
144
+ throw new Error('The absolute VuvoDesk Client update deadline expired before handoff.');
145
145
  }
146
146
  if (previous.operationId !== operationId) {
147
- throw new Error('LiveDesk update handoff was superseded before exact-package launch.');
147
+ throw new Error('VuvoDesk update handoff was superseded before exact-package launch.');
148
148
  }
149
149
  const now = new Date().toISOString();
150
150
  return {
@@ -175,7 +175,7 @@ function submitUpdateHostJob(job) {
175
175
  || status?.protocolVersion !== UPDATE_HOST_PROTOCOL_VERSION
176
176
  || !endpoint
177
177
  || !/^[a-f0-9]{64}$/i.test(token)) {
178
- rejectRequest(new Error('The independent LiveDesk Update Host is unavailable; the existing Client was left running.'));
178
+ rejectRequest(new Error('The independent VuvoDesk Update Host is unavailable; the existing Client was left running.'));
179
179
  return;
180
180
  }
181
181
  const request = JSON.stringify({
@@ -185,7 +185,7 @@ function submitUpdateHostJob(job) {
185
185
  job
186
186
  });
187
187
  if (Buffer.byteLength(request, 'utf8') > UPDATE_HOST_MAX_MESSAGE_BYTES) {
188
- rejectRequest(new Error('The LiveDesk Client update request exceeded the Update Host message limit.'));
188
+ rejectRequest(new Error('The VuvoDesk Client update request exceeded the Update Host message limit.'));
189
189
  return;
190
190
  }
191
191
  const socket = net.createConnection(endpoint);
@@ -207,7 +207,7 @@ function submitUpdateHostJob(job) {
207
207
  socket.on('data', chunk => {
208
208
  responseText += chunk;
209
209
  if (Buffer.byteLength(responseText, 'utf8') > UPDATE_HOST_MAX_MESSAGE_BYTES) {
210
- finish(new Error('The LiveDesk Update Host response exceeded its message limit.'));
210
+ finish(new Error('The VuvoDesk Update Host response exceeded its message limit.'));
211
211
  }
212
212
  });
213
213
  socket.once('error', error => finish(error));
@@ -215,7 +215,7 @@ function submitUpdateHostJob(job) {
215
215
  try {
216
216
  const response = JSON.parse(responseText || '{}');
217
217
  if (response?.ok !== true || response?.accepted !== true || Number(response?.job?.workerPid || 0) <= 1) {
218
- throw new Error(String(response?.error || 'The LiveDesk Update Host did not accept the Client update.'));
218
+ throw new Error(String(response?.error || 'The VuvoDesk Update Host did not accept the Client update.'));
219
219
  }
220
220
  finish(null, response);
221
221
  } catch (error) {
@@ -269,7 +269,7 @@ function buildWorkerInvocation(env) {
269
269
  async function main() {
270
270
  if (!operationId || !versionPattern.test(targetProductVersion)
271
271
  || !Number.isSafeInteger(updateDeadlineEpochMs) || Date.now() >= updateDeadlineEpochMs) {
272
- throw new Error('Invalid or expired LiveDesk exact-package update handoff.');
272
+ throw new Error('Invalid or expired VuvoDesk exact-package update handoff.');
273
273
  }
274
274
  prepareNeutralCwd();
275
275
  writeHandoffStarted();
@@ -289,7 +289,7 @@ async function main() {
289
289
 
290
290
  const invocation = buildWorkerInvocation(env);
291
291
  if (!path.isAbsolute(invocation.command) || !fs.existsSync(invocation.command)) {
292
- throw new Error('LiveDesk could not prepare an absolute Client update worker command for the independent Update Host.');
292
+ throw new Error('VuvoDesk could not prepare an absolute Client update worker command for the independent Update Host.');
293
293
  }
294
294
  const result = await submitUpdateHostJob({
295
295
  jobId: `client-${operationId.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 160)}`,