@livedesk/client 0.1.251 → 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
  }
@@ -7,8 +7,8 @@ import crypto from 'crypto';
7
7
  import { existsSync, promises as fs, statfsSync } from 'fs';
8
8
  import { spawn } from 'child_process';
9
9
  import { createRequire } from 'node:module';
10
- import { fileURLToPath } from 'node:url';
11
- import { resolveAgentShellCommand } from '../src/runtime/agent-shell.js';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { resolveAgentShellCommand } from '../src/runtime/agent-shell.js';
12
12
 
13
13
  const require = createRequire(import.meta.url);
14
14
  const CLIENT_UPDATE_BOOTSTRAP_PATH = fileURLToPath(
@@ -40,7 +40,7 @@ function normalizeClientUpdateTargetVersion(value) {
40
40
  const version = String(value || '').trim().replace(/^v/i, '');
41
41
  if (!version) return '';
42
42
  if (!CLIENT_UPDATE_VERSION_PATTERN.test(version)) {
43
- throw new Error(`Invalid LiveDesk product update version: ${version}`);
43
+ throw new Error(`Invalid VuvoDesk product update version: ${version}`);
44
44
  }
45
45
  return version;
46
46
  }
@@ -64,7 +64,7 @@ async function prepareClientUpdateNeutralCwd(operationId) {
64
64
  const unexpectedEntry = (await fs.readdir(neutralCwd))[0];
65
65
  if (unexpectedEntry) {
66
66
  const shadowPath = path.join(neutralCwd, unexpectedEntry);
67
- 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}`);
68
68
  }
69
69
  return neutralCwd;
70
70
  }
@@ -228,8 +228,8 @@ async function waitForClientUpdateShutdownSignal(
228
228
  return { ready: true, error: '' };
229
229
  }
230
230
  if (state.stage === 'failed') {
231
- const error = String(state.error || 'LiveDesk client update preparation failed.');
232
- 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}`);
233
233
  return { ready: false, error };
234
234
  }
235
235
  } catch {
@@ -239,8 +239,8 @@ async function waitForClientUpdateShutdownSignal(
239
239
  }
240
240
  const deadlineExpired = Date.now() >= Number(updateDeadlineEpochMs);
241
241
  const error = deadlineExpired
242
- ? 'The absolute LiveDesk Client update deadline expired before shutdown.'
243
- : '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.';
244
244
  await writeClientUpdateState('failed', {
245
245
  operationId,
246
246
  error,
@@ -248,7 +248,7 @@ async function waitForClientUpdateShutdownSignal(
248
248
  deadlineExpired,
249
249
  failedAt: new Date().toISOString()
250
250
  }).catch(() => undefined);
251
- 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.');
252
252
  return { ready: false, error };
253
253
  }
254
254
 
@@ -262,7 +262,7 @@ function markClientUpdateConnected() {
262
262
  restartVerified: true,
263
263
  error: ''
264
264
  }).catch(error => {
265
- 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}`);
266
266
  });
267
267
  }
268
268
 
@@ -319,14 +319,14 @@ async function publishClientUpdateSupervisorProof(socket) {
319
319
 
320
320
  function printHelp() {
321
321
  console.log(`
322
- LiveDesk Client
322
+ VuvoDesk Client
323
323
 
324
324
  Usage:
325
325
  npx @livedesk/client connect --manager 127.0.0.1:5197 --pair <token>
326
326
 
327
327
  Options:
328
- --manager <host:port> LiveDesk Hub address. Default: ${DEFAULT_MANAGER}
329
- --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.
330
330
  --slot <number> Screen wall slot number for this computer.
331
331
  --name <name> Friendly device name. Default: OS hostname.
332
332
  --heartbeat <ms> Status heartbeat interval. Default: ${DEFAULT_HEARTBEAT_MS}
@@ -337,7 +337,7 @@ Options:
337
337
  --no-live Disable focused live screen streaming.
338
338
  --tasks Enable safe remote task inbox. Default on.
339
339
  --no-tasks Disable remote task dispatch capability.
340
- --files-dir <path> Default folder for received files. Default: ~/Desktop/LiveDeskFiles.
340
+ --files-dir <path> Default folder for received files. Default: ~/Desktop/VuvoDeskFiles.
341
341
  --fake-thumbnail Use generated thumbnail frames for smoke tests.
342
342
  --exit-on-disconnect Exit after Hub disconnect. Useful for tests.
343
343
  --exit-on-invalid-pair Exit when the Hub rejects the pair token.
@@ -685,7 +685,7 @@ async function waitForNodeServiceState(executable, service, expectedState, timeo
685
685
  }
686
686
 
687
687
  function getDefaultFilesDir() {
688
- return path.join(os.homedir(), 'Desktop', 'LiveDeskFiles');
688
+ return path.join(os.homedir(), 'Desktop', 'VuvoDeskFiles');
689
689
  }
690
690
 
691
691
  function normalizeDirectoryPath(value, fallback = getDefaultFilesDir()) {
@@ -862,7 +862,7 @@ function createFakeThumbnailFrame(options, payload, frameSeq) {
862
862
  '<rect width="100%" height="100%" fill="#0f172a"/>',
863
863
  '<rect x="14" y="14" width="calc(100% - 28px)" height="calc(100% - 28px)" rx="12" fill="#1e293b" stroke="#38bdf8" stroke-width="2"/>',
864
864
  `<text x="28" y="48" fill="#e2e8f0" font-family="Arial, sans-serif" font-size="20" font-weight="700">${title}</text>`,
865
- `<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>`,
866
866
  `<text x="28" y="${height - 30}" fill="#94a3b8" font-family="Consolas, monospace" font-size="12">${now}</text>`,
867
867
  '</svg>'
868
868
  ].join('');
@@ -1119,7 +1119,7 @@ async function assertAgentPathDoesNotTraverseLink(base, resolved) {
1119
1119
  }
1120
1120
  const canonicalCurrent = await fs.realpath(current);
1121
1121
  if (!isPathWithinAgentRoot(canonicalRoot, canonicalCurrent)) {
1122
- 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.');
1123
1123
  }
1124
1124
  return;
1125
1125
  } catch (error) {
@@ -1137,16 +1137,16 @@ async function resolveAgentPath(options, value, permissionMode, rejectSensitive
1137
1137
  if (!text || text.length > 600) throw new Error('path is invalid');
1138
1138
  const resolved = path.resolve(path.isAbsolute(text) ? text : path.join(base, text));
1139
1139
  const relative = path.relative(base, resolved);
1140
- 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');
1141
1141
  if (permissionMode !== 'full-access') await assertAgentPathDoesNotTraverseLink(base, resolved);
1142
1142
  if (rejectSensitive && isSensitiveAgentPath(resolved)) throw new Error('credential and secret paths are not available');
1143
1143
  return resolved;
1144
1144
  }
1145
1145
 
1146
- function runNodeAgentProcess(executable, args, timeoutMs = 15000, cwd = undefined, maxTimeoutMs = 30000) {
1147
- return new Promise((resolve, reject) => {
1148
- const startedAt = Date.now();
1149
- const child = spawn(executable, args, { cwd, windowsHide: true, shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
1146
+ function runNodeAgentProcess(executable, args, timeoutMs = 15000, cwd = undefined, maxTimeoutMs = 30000) {
1147
+ return new Promise((resolve, reject) => {
1148
+ const startedAt = Date.now();
1149
+ const child = spawn(executable, args, { cwd, windowsHide: true, shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
1150
1150
  let output = '';
1151
1151
  let timedOut = false;
1152
1152
  const append = chunk => { output = `${output}${String(chunk || '')}`.replace(/[\0\r]/g, ' ').slice(0, MAX_AGENT_OUTPUT_CHARS); };
@@ -1156,55 +1156,55 @@ function runNodeAgentProcess(executable, args, timeoutMs = 15000, cwd = undefine
1156
1156
  timedOut = true;
1157
1157
  child.kill('SIGTERM');
1158
1158
  setTimeout(() => child.kill('SIGKILL'), 1000).unref?.();
1159
- }, Math.max(1000, Math.min(maxTimeoutMs, Number(timeoutMs) || 15000)));
1160
- child.once('error', error => { clearTimeout(timer); reject(error); });
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 }); });
1162
- });
1163
- }
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
- }
1159
+ }, Math.max(1000, Math.min(maxTimeoutMs, Number(timeoutMs) || 15000)));
1160
+ child.once('error', error => { clearTimeout(timer); reject(error); });
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 }); });
1162
+ });
1163
+ }
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
1208
 
1209
1209
  function redactAgentOutput(value) {
1210
1210
  let output = String(value || '').replace(/[\0\r]/g, ' ');
@@ -1330,7 +1330,7 @@ async function persistNodeEnvironmentVariable(name, value) {
1330
1330
  return { persisted: true, scope: 'user', output: result.output };
1331
1331
  }
1332
1332
  const profilePath = path.join(os.homedir(), '.profile');
1333
- const marker = `# LiveDesk managed environment: ${variableName}`;
1333
+ const marker = `# VuvoDesk managed environment: ${variableName}`;
1334
1334
  const quoted = `'${variableValue.replaceAll("'", "'\\\"'\\\"'")}'`;
1335
1335
  let profile = '';
1336
1336
  try { profile = await fs.readFile(profilePath, 'utf8'); } catch { /* create on demand */ }
@@ -1370,7 +1370,7 @@ async function executeNodeAgentOperation(options, operation, payload = {}) {
1370
1370
  else await fs.unlink(filePath);
1371
1371
  return { summary: `Deleted ${path.basename(filePath)}.`, data: { path: filePath, recursive: args.recursive === true } };
1372
1372
  }
1373
- if (operation === 'file.list') {
1373
+ if (operation === 'file.list') {
1374
1374
  const directory = await resolveAgentPath(options, args.path, permissionMode);
1375
1375
  const entries = await fs.readdir(directory, { withFileTypes: true });
1376
1376
  const maxEntries = Math.max(1, Math.min(500, Number(args.maxEntries) || 200));
@@ -1380,15 +1380,15 @@ async function executeNodeAgentOperation(options, operation, payload = {}) {
1380
1380
  if (isSensitiveAgentPath(entryPath)) continue;
1381
1381
  data.push({ name: entry.name, path: entryPath, type: entry.isDirectory() ? 'directory' : 'file', sizeBytes: entry.isDirectory() ? 0 : (await fs.stat(entryPath)).size });
1382
1382
  }
1383
- return { summary: `Listed ${data.length} entries.`, data: { path: directory, entries: data, truncated: entries.length > maxEntries } };
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
- }
1383
+ return { summary: `Listed ${data.length} entries.`, data: { path: directory, entries: data, truncated: entries.length > maxEntries } };
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
+ }
1392
1392
  if (operation === 'application.launch') {
1393
1393
  const executable = String(args.executable || '').trim();
1394
1394
  if (!executable || executable.length > 400 || /[\0\r\n]/.test(executable)) throw new Error('executable is invalid');
@@ -1442,13 +1442,13 @@ async function executeNodeAgentOperation(options, operation, payload = {}) {
1442
1442
  if (!ok) throw new Error(`Service ${action} failed for ${service}.`);
1443
1443
  return { summary: `Service ${action} completed for ${service}.`, data: { service, action, results } };
1444
1444
  }
1445
- if (operation === 'command.run') {
1446
- const command = String(args.command || '');
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() } };
1445
+ if (operation === 'command.run') {
1446
+ const command = String(args.command || '');
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() } };
1452
1452
  }
1453
1453
  if (operation === 'script.run') {
1454
1454
  const scriptPath = await resolveAgentPath(options, args.path, permissionMode);
@@ -1535,7 +1535,7 @@ function normalizeNodeAgentTaskResult(result) {
1535
1535
  return { ...result, ok, status: ok ? 'completed' : 'failed', error };
1536
1536
  }
1537
1537
 
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']);
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']);
1539
1539
 
1540
1540
  function remotePolicyAllows(options, command) {
1541
1541
  const policy = options.effectivePolicy;
@@ -1587,7 +1587,7 @@ async function scheduleClientUpdate(options, payload = {}) {
1587
1587
  if (!Number.isSafeInteger(updateTimeoutMs)
1588
1588
  || updateTimeoutMs < 1_000
1589
1589
  || updateTimeoutMs > 90 * 60_000) {
1590
- throw new Error('LiveDesk Client update requires a bounded deadline budget.');
1590
+ throw new Error('VuvoDesk Client update requires a bounded deadline budget.');
1591
1591
  }
1592
1592
  updateDeadlineEpochMs = Date.now() + updateTimeoutMs;
1593
1593
  const originalCwd = path.resolve(process.cwd());
@@ -1608,7 +1608,7 @@ async function scheduleClientUpdate(options, payload = {}) {
1608
1608
  ? configuredParentPid
1609
1609
  : process.pid;
1610
1610
  if (!Number.isInteger(parentPid) || parentPid <= 1) {
1611
- throw new Error('LiveDesk client launcher process id is unavailable.');
1611
+ throw new Error('VuvoDesk client launcher process id is unavailable.');
1612
1612
  }
1613
1613
  const nodeBinDir = path.dirname(process.execPath);
1614
1614
  const npxCandidates = process.platform === 'win32'
@@ -1616,7 +1616,7 @@ async function scheduleClientUpdate(options, payload = {}) {
1616
1616
  : [process.env.LIVEDESK_NPX_EXECUTABLE, path.join(nodeBinDir, 'npx'), 'npx'];
1617
1617
  const command = npxCandidates.find(candidate => candidate && (!path.isAbsolute(candidate) || existsSync(candidate)));
1618
1618
  if (!command) {
1619
- throw new Error('LiveDesk npx executable was not found.');
1619
+ throw new Error('VuvoDesk npx executable was not found.');
1620
1620
  }
1621
1621
  const frozenCommand = path.isAbsolute(command) || !/[\\/]/.test(command)
1622
1622
  ? command
@@ -1651,7 +1651,7 @@ async function scheduleClientUpdate(options, payload = {}) {
1651
1651
  }, neutralCwd);
1652
1652
  const bootstrapStat = await fs.stat(CLIENT_UPDATE_BOOTSTRAP_PATH);
1653
1653
  if (!bootstrapStat.isFile() || bootstrapStat.size <= 0) {
1654
- 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}`);
1655
1655
  }
1656
1656
  return await new Promise((resolve, reject) => {
1657
1657
  const child = spawn(process.execPath, [CLIENT_UPDATE_BOOTSTRAP_PATH], {
@@ -1777,7 +1777,7 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
1777
1777
  summary: result.summary,
1778
1778
  error: result.error || undefined,
1779
1779
  data: result.data,
1780
- sideEffects: ['file.read', 'file.list', 'file.search', 'network.status', 'logs.collect'].includes(command) ? 'none' : 'audited',
1780
+ sideEffects: ['file.read', 'file.list', 'file.search', 'network.status', 'logs.collect'].includes(command) ? 'none' : 'audited',
1781
1781
  completedAt: new Date().toISOString()
1782
1782
  }
1783
1783
  });
@@ -2008,7 +2008,7 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
2008
2008
  function connectOnce(options, deviceId) {
2009
2009
  const manager = parseManagerAddress(options.manager);
2010
2010
  if (!isPrivateLanHost(manager.host) && !isTruthy(process.env.LIVEDESK_ALLOW_UNENCRYPTED_LAN)) {
2011
- 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.'));
2012
2012
  }
2013
2013
  return new Promise((resolve, reject) => {
2014
2014
  const socket = net.createConnection({
@@ -2106,11 +2106,11 @@ function connectOnce(options, deviceId) {
2106
2106
  options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
2107
2107
  ? message.effectivePolicy
2108
2108
  : null;
2109
- console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
2109
+ console.log(`Connected to VuvoDesk Hub as ${options.name} (${deviceId})`);
2110
2110
  markClientUpdateConnected();
2111
2111
  void publishClientUpdateSupervisorProof(socket).catch(error => {
2112
2112
  if (!socket.destroyed) {
2113
- console.error(`LiveDesk client update verification proof failed: ${error?.message || error}`);
2113
+ console.error(`VuvoDesk client update verification proof failed: ${error?.message || error}`);
2114
2114
  }
2115
2115
  });
2116
2116
  writeJsonLine(socket, {
@@ -2147,7 +2147,7 @@ function connectOnce(options, deviceId) {
2147
2147
  finish();
2148
2148
  return;
2149
2149
  } else if (message.type === 'error') {
2150
- const remoteError = new Error(message.error || 'LiveDesk Hub rejected the connection.');
2150
+ const remoteError = new Error(message.error || 'VuvoDesk Hub rejected the connection.');
2151
2151
  if (message.error === 'invalid-pair-token' && options.exitOnInvalidPair) {
2152
2152
  remoteError.exitCode = EXIT_INVALID_PAIR_TOKEN;
2153
2153
  }
@@ -2164,7 +2164,7 @@ function connectOnce(options, deviceId) {
2164
2164
 
2165
2165
  async function connectWithRetry(options) {
2166
2166
  if (!options.pair) {
2167
- 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.');
2168
2168
  }
2169
2169
 
2170
2170
  const deviceId = await getDeviceId(options.deviceId);
@@ -2173,7 +2173,7 @@ async function connectWithRetry(options) {
2173
2173
 
2174
2174
  process.once('SIGINT', () => {
2175
2175
  stopping = true;
2176
- console.log('\nStopping LiveDesk Client...');
2176
+ console.log('\nStopping VuvoDesk Client...');
2177
2177
  });
2178
2178
  process.once('SIGTERM', () => {
2179
2179
  stopping = true;
@@ -2192,7 +2192,7 @@ async function connectWithRetry(options) {
2192
2192
  throw err;
2193
2193
  }
2194
2194
  const delayMs = DEFAULT_RECONNECT_MS;
2195
- 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.`);
2196
2196
  await wait(delayMs);
2197
2197
  }
2198
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)}`,