@livedesk/client 0.1.62 → 0.1.64

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.
@@ -12,8 +12,8 @@ const DEFAULT_HEARTBEAT_MS = 5000;
12
12
  const DEFAULT_RECONNECT_MS = 5000;
13
13
  const EXIT_INVALID_PAIR_TOKEN = 23;
14
14
  const DEFAULT_AI_MODEL = 'gpt-5.4-mini';
15
- const DEFAULT_LIVE_FPS = 20;
16
- const MAX_LIVE_FPS = 24;
15
+ const DEFAULT_LIVE_FPS = 30;
16
+ const MAX_LIVE_FPS = 30;
17
17
  const MAX_FRAME_BASE64_CHARS = 3 * 1024 * 1024;
18
18
  const MAX_AI_OUTPUT_CHARS = 6000;
19
19
  const MAX_FILE_TRANSFER_FILES = 24;
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statfsSync, 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';
@@ -1035,16 +1035,148 @@ function getDashboardAuthLabel(choice, loggedOut = false) {
1035
1035
  return email ? `Google - ${email}` : 'Google signed in';
1036
1036
  }
1037
1037
 
1038
+ function formatDashboardBytes(value) {
1039
+ const bytes = Number(value || 0);
1040
+ if (!Number.isFinite(bytes) || bytes <= 0) {
1041
+ return '-';
1042
+ }
1043
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
1044
+ let next = bytes;
1045
+ let unitIndex = 0;
1046
+ while (next >= 1024 && unitIndex < units.length - 1) {
1047
+ next /= 1024;
1048
+ unitIndex += 1;
1049
+ }
1050
+ const precision = next >= 100 || unitIndex === 0 ? 0 : next >= 10 ? 1 : 2;
1051
+ return `${next.toFixed(precision)} ${units[unitIndex]}`;
1052
+ }
1053
+
1054
+ function formatDashboardDuration(seconds) {
1055
+ const totalSeconds = Math.max(0, Math.floor(Number(seconds || 0)));
1056
+ const days = Math.floor(totalSeconds / 86400);
1057
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
1058
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
1059
+ if (days > 0) {
1060
+ return `${days}d ${hours}h`;
1061
+ }
1062
+ if (hours > 0) {
1063
+ return `${hours}h ${minutes}m`;
1064
+ }
1065
+ return `${minutes}m`;
1066
+ }
1067
+
1068
+ function getDashboardNetworkInfo() {
1069
+ const rows = [];
1070
+ const interfaces = os.networkInterfaces();
1071
+ for (const [name, addresses] of Object.entries(interfaces)) {
1072
+ for (const address of addresses || []) {
1073
+ if (!address || address.internal) {
1074
+ continue;
1075
+ }
1076
+ rows.push({
1077
+ name,
1078
+ family: address.family,
1079
+ address: address.address,
1080
+ cidr: address.cidr || ''
1081
+ });
1082
+ }
1083
+ }
1084
+ return rows.slice(0, 8);
1085
+ }
1086
+
1087
+ function getDashboardDiskInfo() {
1088
+ try {
1089
+ const stats = statfsSync(os.homedir());
1090
+ const totalBytes = Number(stats.blocks || 0) * Number(stats.bsize || 0);
1091
+ const freeBytes = Number(stats.bavail || stats.bfree || 0) * Number(stats.bsize || 0);
1092
+ const usedBytes = Math.max(0, totalBytes - freeBytes);
1093
+ return {
1094
+ path: os.homedir(),
1095
+ total: formatDashboardBytes(totalBytes),
1096
+ free: formatDashboardBytes(freeBytes),
1097
+ used: formatDashboardBytes(usedBytes),
1098
+ usedPercent: totalBytes > 0 ? Math.round(usedBytes * 100 / totalBytes) : 0
1099
+ };
1100
+ } catch {
1101
+ return {
1102
+ path: os.homedir(),
1103
+ total: '-',
1104
+ free: '-',
1105
+ used: '-',
1106
+ usedPercent: 0
1107
+ };
1108
+ }
1109
+ }
1110
+
1111
+ function getDashboardUsername() {
1112
+ try {
1113
+ return os.userInfo().username || process.env.USERNAME || process.env.USER || '';
1114
+ } catch {
1115
+ return process.env.USERNAME || process.env.USER || '';
1116
+ }
1117
+ }
1118
+
1119
+ function getDashboardSystemInfo(state = {}) {
1120
+ const totalMem = os.totalmem();
1121
+ const freeMem = os.freemem();
1122
+ const usedMem = Math.max(0, totalMem - freeMem);
1123
+ const cpus = os.cpus();
1124
+ const load = os.loadavg().map(value => Number.isFinite(value) ? Number(value.toFixed(2)) : 0);
1125
+ const fastRuntime = getFastRuntime();
1126
+ const fastPackaged = fastRuntime
1127
+ ? (hasFastExecutable(fastRuntime) || hasFastDll(fastRuntime))
1128
+ : false;
1129
+ const agent = state.agent || {};
1130
+ return {
1131
+ hostname: os.hostname(),
1132
+ platform: os.platform(),
1133
+ release: os.release(),
1134
+ arch: os.arch(),
1135
+ type: os.type(),
1136
+ user: getDashboardUsername(),
1137
+ home: os.homedir(),
1138
+ cwd: process.cwd(),
1139
+ node: process.version,
1140
+ packageVersion: readPackageVersion(),
1141
+ pid: process.pid,
1142
+ processUptime: formatDashboardDuration(process.uptime()),
1143
+ systemUptime: formatDashboardDuration(os.uptime()),
1144
+ cpuModel: cpus[0]?.model || '-',
1145
+ cpuCores: cpus.length,
1146
+ loadAverage: load.join(' / '),
1147
+ memory: {
1148
+ total: formatDashboardBytes(totalMem),
1149
+ free: formatDashboardBytes(freeMem),
1150
+ used: formatDashboardBytes(usedMem),
1151
+ usedPercent: totalMem > 0 ? Math.round(usedMem * 100 / totalMem) : 0
1152
+ },
1153
+ disk: getDashboardDiskInfo(),
1154
+ network: getDashboardNetworkInfo(),
1155
+ runtime: {
1156
+ requestedEngine: agent.requestedEngine || '',
1157
+ activeEngine: agent.engine || 'pending',
1158
+ state: agent.state || 'waiting',
1159
+ pid: agent.pid || '',
1160
+ command: agent.command || '',
1161
+ args: Array.isArray(agent.args) ? agent.args.join(' ') : '',
1162
+ runtimeId: agent.runtimeId || fastRuntime?.rid || '',
1163
+ fastPackaged,
1164
+ startedAt: agent.startedAt || ''
1165
+ }
1166
+ };
1167
+ }
1168
+
1038
1169
  function renderConnectionDashboardPage(state = {}) {
1039
1170
  const choice = state.choice || null;
1040
1171
  const connectedAt = state.connectedAt || new Date().toISOString();
1041
1172
  const authLabel = getDashboardAuthLabel(choice, state.loggedOut);
1042
1173
  const manager = state.manager || (choice?.manager || '');
1174
+ const agentState = state.agent?.state || '';
1043
1175
  const slot = normalizeSlotNumber(state.slot) ? `Slot ${String(state.slot).padStart(3, '0')}` : 'First available';
1044
1176
  const statusLabel = state.loggedOut
1045
1177
  ? 'Signed out for next restart'
1046
1178
  : manager
1047
- ? 'Client starting'
1179
+ ? agentState === 'running' ? 'Agent running' : 'Client starting'
1048
1180
  : 'Finding Hub';
1049
1181
  const message = state.message || (manager
1050
1182
  ? 'The LiveDesk client is running from the terminal. Keep this tab open as the local client dashboard.'
@@ -1057,7 +1189,8 @@ function renderConnectionDashboardPage(state = {}) {
1057
1189
  slot,
1058
1190
  startup: Boolean(state.startup),
1059
1191
  completed: true,
1060
- statusLabel
1192
+ statusLabel,
1193
+ system: getDashboardSystemInfo(state)
1061
1194
  }).replaceAll('<', '\\u003c');
1062
1195
 
1063
1196
  return `<!doctype html>
@@ -1073,22 +1206,24 @@ function renderConnectionDashboardPage(state = {}) {
1073
1206
  body {
1074
1207
  min-height: 100vh;
1075
1208
  display: grid;
1076
- place-items: center;
1077
- padding: 28px;
1209
+ align-items: stretch;
1210
+ padding: 18px;
1078
1211
  background:
1079
- radial-gradient(circle at 78% 10%, rgba(203, 213, 225, 0.46), transparent 24%),
1212
+ radial-gradient(circle at 84% 0%, rgba(203, 213, 225, 0.36), transparent 26%),
1080
1213
  linear-gradient(180deg, #f8fafc, #eef2f7);
1081
1214
  color: #111827;
1082
1215
  }
1083
1216
  main {
1084
- width: min(920px, 100%);
1217
+ width: min(1280px, 100%);
1218
+ min-height: calc(100vh - 36px);
1219
+ margin: 0 auto;
1085
1220
  display: grid;
1086
- grid-template-columns: minmax(280px, 0.88fr) minmax(320px, 1.12fr);
1087
- overflow: hidden;
1221
+ grid-template-columns: minmax(286px, 0.74fr) minmax(640px, 1.26fr);
1088
1222
  border: 1px solid rgba(148, 163, 184, 0.28);
1089
- border-radius: 18px;
1223
+ border-radius: 20px;
1090
1224
  background: rgba(255, 255, 255, 0.92);
1091
1225
  box-shadow: 0 36px 110px rgba(15, 23, 42, 0.14);
1226
+ overflow: hidden;
1092
1227
  }
1093
1228
  .hero {
1094
1229
  display: grid;
@@ -1144,10 +1279,14 @@ function renderConnectionDashboardPage(state = {}) {
1144
1279
  }
1145
1280
  .panel {
1146
1281
  display: grid;
1147
- gap: 16px;
1148
- padding: 34px;
1282
+ align-content: start;
1283
+ gap: 14px;
1284
+ padding: 24px;
1285
+ background:
1286
+ linear-gradient(180deg, rgba(248, 250, 252, 0.78), rgba(241, 245, 249, 0.72)),
1287
+ #f8fafc;
1149
1288
  }
1150
- .status-card {
1289
+ .status-card, .section-card {
1151
1290
  display: grid;
1152
1291
  gap: 14px;
1153
1292
  padding: 18px;
@@ -1158,6 +1297,7 @@ function renderConnectionDashboardPage(state = {}) {
1158
1297
  0 20px 50px rgba(15, 23, 42, 0.09),
1159
1298
  0 1px 0 rgba(255, 255, 255, 0.9) inset;
1160
1299
  }
1300
+ .section-card { gap: 12px; }
1161
1301
  .status-head {
1162
1302
  display: flex;
1163
1303
  align-items: center;
@@ -1191,6 +1331,17 @@ function renderConnectionDashboardPage(state = {}) {
1191
1331
  font-size: 20px;
1192
1332
  line-height: 1.18;
1193
1333
  }
1334
+ .section-title {
1335
+ display: flex;
1336
+ align-items: center;
1337
+ justify-content: space-between;
1338
+ gap: 12px;
1339
+ color: #0f172a;
1340
+ font-size: 12px;
1341
+ font-weight: 850;
1342
+ letter-spacing: 0.04em;
1343
+ text-transform: uppercase;
1344
+ }
1194
1345
  dl {
1195
1346
  display: grid;
1196
1347
  grid-template-columns: 118px minmax(0, 1fr);
@@ -1213,6 +1364,91 @@ function renderConnectionDashboardPage(state = {}) {
1213
1364
  text-overflow: ellipsis;
1214
1365
  white-space: nowrap;
1215
1366
  }
1367
+ .hero-metrics, .metric-grid {
1368
+ display: grid;
1369
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1370
+ gap: 10px;
1371
+ }
1372
+ .hero-metric, .metric-card {
1373
+ display: grid;
1374
+ gap: 6px;
1375
+ min-height: 68px;
1376
+ padding: 12px;
1377
+ border: 1px solid rgba(148, 163, 184, 0.24);
1378
+ border-radius: 12px;
1379
+ background: rgba(255, 255, 255, 0.78);
1380
+ box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
1381
+ }
1382
+ .hero-metric span, .metric-card span {
1383
+ color: #64748b;
1384
+ font-size: 11px;
1385
+ font-weight: 780;
1386
+ letter-spacing: 0.04em;
1387
+ text-transform: uppercase;
1388
+ }
1389
+ .hero-metric strong, .metric-card strong {
1390
+ min-width: 0;
1391
+ overflow: hidden;
1392
+ color: #0f172a;
1393
+ font-size: 18px;
1394
+ font-weight: 780;
1395
+ text-overflow: ellipsis;
1396
+ white-space: nowrap;
1397
+ }
1398
+ .progress {
1399
+ width: 100%;
1400
+ height: 4px;
1401
+ overflow: hidden;
1402
+ border-radius: 999px;
1403
+ background: #e2e8f0;
1404
+ }
1405
+ .progress i {
1406
+ display: block;
1407
+ width: 0%;
1408
+ height: 100%;
1409
+ border-radius: inherit;
1410
+ background: #111827;
1411
+ transition: width 180ms ease;
1412
+ }
1413
+ .info-grid {
1414
+ display: grid;
1415
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1416
+ gap: 9px 14px;
1417
+ }
1418
+ .info-item {
1419
+ min-width: 0;
1420
+ display: grid;
1421
+ gap: 3px;
1422
+ }
1423
+ .info-item span, .network-row span {
1424
+ color: #64748b;
1425
+ font-size: 11px;
1426
+ font-weight: 760;
1427
+ }
1428
+ .info-item strong, .network-row strong {
1429
+ min-width: 0;
1430
+ overflow: hidden;
1431
+ color: #111827;
1432
+ font-size: 13px;
1433
+ font-weight: 720;
1434
+ text-overflow: ellipsis;
1435
+ white-space: nowrap;
1436
+ }
1437
+ .network-list {
1438
+ display: grid;
1439
+ gap: 7px;
1440
+ }
1441
+ .network-row {
1442
+ display: grid;
1443
+ grid-template-columns: minmax(88px, 0.45fr) minmax(0, 1fr);
1444
+ gap: 12px;
1445
+ min-height: 32px;
1446
+ align-items: center;
1447
+ padding: 8px 10px;
1448
+ border: 1px solid rgba(148, 163, 184, 0.24);
1449
+ border-radius: 9px;
1450
+ background: #f8fafc;
1451
+ }
1216
1452
  .actions {
1217
1453
  display: grid;
1218
1454
  grid-template-columns: 1fr 1fr;
@@ -1256,6 +1492,7 @@ function renderConnectionDashboardPage(state = {}) {
1256
1492
  .hero, .panel { padding: 24px; }
1257
1493
  h1 { font-size: 28px; }
1258
1494
  .actions { grid-template-columns: 1fr; }
1495
+ .hero-metrics, .metric-grid, .info-grid { grid-template-columns: 1fr; }
1259
1496
  }
1260
1497
  </style>
1261
1498
  </head>
@@ -1274,6 +1511,26 @@ function renderConnectionDashboardPage(state = {}) {
1274
1511
  <h1>Client is ready</h1>
1275
1512
  <p id="message">${escapeHtml(message)}</p>
1276
1513
  </div>
1514
+ <div class="hero-metrics">
1515
+ <div class="hero-metric">
1516
+ <span>Host</span>
1517
+ <strong id="hero-host">-</strong>
1518
+ </div>
1519
+ <div class="hero-metric">
1520
+ <span>Engine</span>
1521
+ <strong id="hero-engine">pending</strong>
1522
+ </div>
1523
+ <div class="hero-metric">
1524
+ <span>Memory</span>
1525
+ <strong id="hero-memory">-</strong>
1526
+ <div class="progress"><i id="memory-bar"></i></div>
1527
+ </div>
1528
+ <div class="hero-metric">
1529
+ <span>Disk</span>
1530
+ <strong id="hero-disk">-</strong>
1531
+ <div class="progress"><i id="disk-bar"></i></div>
1532
+ </div>
1533
+ </div>
1277
1534
  <p>Keep this page open if you want a local control surface for this workstation. Reboots can reconnect automatically when startup is enabled.</p>
1278
1535
  </section>
1279
1536
  <section class="panel">
@@ -1290,6 +1547,47 @@ function renderConnectionDashboardPage(state = {}) {
1290
1547
  <dt>Started</dt><dd id="connected-at">${escapeHtml(connectedAt)}</dd>
1291
1548
  </dl>
1292
1549
  </article>
1550
+ <article class="section-card">
1551
+ <div class="section-title">System</div>
1552
+ <div class="metric-grid">
1553
+ <div class="metric-card"><span>CPU</span><strong id="cpu-model">-</strong></div>
1554
+ <div class="metric-card"><span>Cores</span><strong id="cpu-cores">-</strong></div>
1555
+ <div class="metric-card"><span>OS</span><strong id="os-line">-</strong></div>
1556
+ <div class="metric-card"><span>Uptime</span><strong id="system-uptime">-</strong></div>
1557
+ <div class="metric-card"><span>Load avg</span><strong id="load-average">-</strong></div>
1558
+ <div class="metric-card"><span>Architecture</span><strong id="arch-line">-</strong></div>
1559
+ </div>
1560
+ </article>
1561
+ <article class="section-card">
1562
+ <div class="section-title">Runtime</div>
1563
+ <div class="info-grid">
1564
+ <div class="info-item"><span>Agent state</span><strong id="agent-state">waiting</strong></div>
1565
+ <div class="info-item"><span>Agent PID</span><strong id="agent-pid">-</strong></div>
1566
+ <div class="info-item"><span>Runtime</span><strong id="runtime-id">-</strong></div>
1567
+ <div class="info-item"><span>Fast package</span><strong id="fast-packaged">-</strong></div>
1568
+ <div class="info-item"><span>Client PID</span><strong id="client-pid">-</strong></div>
1569
+ <div class="info-item"><span>Node</span><strong id="node-version">-</strong></div>
1570
+ <div class="info-item"><span>Package</span><strong id="package-version">-</strong></div>
1571
+ <div class="info-item"><span>Process uptime</span><strong id="process-uptime">-</strong></div>
1572
+ <div class="info-item"><span>Agent started</span><strong id="agent-started">-</strong></div>
1573
+ </div>
1574
+ </article>
1575
+ <article class="section-card">
1576
+ <div class="section-title">Storage & Paths</div>
1577
+ <div class="info-grid">
1578
+ <div class="info-item"><span>Disk used</span><strong id="disk-used">-</strong></div>
1579
+ <div class="info-item"><span>Disk free</span><strong id="disk-free">-</strong></div>
1580
+ <div class="info-item"><span>User</span><strong id="user-name">-</strong></div>
1581
+ <div class="info-item"><span>Home</span><strong id="home-path">-</strong></div>
1582
+ <div class="info-item"><span>Working dir</span><strong id="cwd-path">-</strong></div>
1583
+ <div class="info-item"><span>Agent command</span><strong id="agent-command">-</strong></div>
1584
+ <div class="info-item"><span>Agent args</span><strong id="agent-args">-</strong></div>
1585
+ </div>
1586
+ </article>
1587
+ <article class="section-card">
1588
+ <div class="section-title">Network</div>
1589
+ <div id="network-list" class="network-list"></div>
1590
+ </article>
1293
1591
  <div class="actions">
1294
1592
  <a class="button secondary" href="/">Refresh dashboard</a>
1295
1593
  <form method="post" action="/logout">
@@ -1305,7 +1603,33 @@ function renderConnectionDashboardPage(state = {}) {
1305
1603
  const node = document.getElementById(id);
1306
1604
  if (node) node.textContent = value || '';
1307
1605
  }
1606
+ function setBar(id, value) {
1607
+ const node = document.getElementById(id);
1608
+ if (!node) return;
1609
+ const percent = Math.max(0, Math.min(100, Number(value) || 0));
1610
+ node.style.width = percent + '%';
1611
+ }
1612
+ function renderNetworkRows(items) {
1613
+ const list = document.getElementById('network-list');
1614
+ if (!list) return;
1615
+ list.textContent = '';
1616
+ const rows = Array.isArray(items) && items.length > 0 ? items : [{ name: 'Network', address: 'not reported', family: '' }];
1617
+ for (const item of rows) {
1618
+ const row = document.createElement('div');
1619
+ row.className = 'network-row';
1620
+ const name = document.createElement('span');
1621
+ name.textContent = [item.name, item.family].filter(Boolean).join(' / ');
1622
+ const address = document.createElement('strong');
1623
+ address.textContent = item.cidr || item.address || 'not reported';
1624
+ row.append(name, address);
1625
+ list.append(row);
1626
+ }
1627
+ }
1308
1628
  function applyState(state) {
1629
+ const system = state.system || {};
1630
+ const memory = system.memory || {};
1631
+ const disk = system.disk || {};
1632
+ const runtime = system.runtime || {};
1309
1633
  setText('status', state.statusLabel || initialState.statusLabel);
1310
1634
  setText('auth', state.authLabel || initialState.authLabel);
1311
1635
  setText('manager', state.manager || 'waiting');
@@ -1313,6 +1637,35 @@ function renderConnectionDashboardPage(state = {}) {
1313
1637
  setText('startup', state.startup ? 'enabled' : 'manual');
1314
1638
  setText('connected-at', state.connectedAt || initialState.connectedAt);
1315
1639
  setText('message', state.message || initialState.message);
1640
+ setText('hero-host', system.hostname || '-');
1641
+ setText('hero-engine', runtime.activeEngine || runtime.state || 'pending');
1642
+ setText('hero-memory', memory.used && memory.total ? memory.used + ' / ' + memory.total : '-');
1643
+ setText('hero-disk', disk.usedPercent ? disk.usedPercent + '% used' : disk.free ? disk.free + ' free' : '-');
1644
+ setBar('memory-bar', memory.usedPercent);
1645
+ setBar('disk-bar', disk.usedPercent);
1646
+ setText('cpu-model', system.cpuModel || '-');
1647
+ setText('cpu-cores', system.cpuCores ? String(system.cpuCores) : '-');
1648
+ setText('os-line', [system.type || system.platform, system.release, system.arch].filter(Boolean).join(' '));
1649
+ setText('system-uptime', system.systemUptime || '-');
1650
+ setText('load-average', system.loadAverage || '-');
1651
+ setText('arch-line', [system.platform, system.arch].filter(Boolean).join(' / '));
1652
+ setText('agent-state', runtime.state || 'waiting');
1653
+ setText('agent-pid', runtime.pid ? String(runtime.pid) : '-');
1654
+ setText('runtime-id', runtime.runtimeId || runtime.requestedEngine || '-');
1655
+ setText('fast-packaged', runtime.fastPackaged ? 'available' : 'missing');
1656
+ setText('client-pid', system.pid ? String(system.pid) : '-');
1657
+ setText('node-version', system.node || '-');
1658
+ setText('package-version', system.packageVersion || '-');
1659
+ setText('process-uptime', system.processUptime || '-');
1660
+ setText('agent-started', runtime.startedAt || '-');
1661
+ setText('disk-used', disk.used && disk.total ? disk.used + ' / ' + disk.total : '-');
1662
+ setText('disk-free', disk.free || '-');
1663
+ setText('user-name', system.user || '-');
1664
+ setText('home-path', system.home || '-');
1665
+ setText('cwd-path', system.cwd || '-');
1666
+ setText('agent-command', runtime.command || '-');
1667
+ setText('agent-args', runtime.args || '-');
1668
+ renderNetworkRows(system.network);
1316
1669
  }
1317
1670
  applyState(initialState);
1318
1671
  if (location.pathname === '/callback') {
@@ -1458,6 +1811,10 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1458
1811
  let completedTitle = 'LiveDesk connection complete';
1459
1812
  let completedMessage = 'The LiveDesk client is starting automatically.';
1460
1813
  const dashboardState = {
1814
+ agent: {
1815
+ requestedEngine: options.engine || '',
1816
+ state: 'waiting'
1817
+ },
1461
1818
  choice: null,
1462
1819
  connectedAt: '',
1463
1820
  endpointCandidates: [],
@@ -1506,6 +1863,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1506
1863
  });
1507
1864
  const getDashboardApiState = () => {
1508
1865
  const manager = dashboardState.manager || dashboardState.choice?.manager || '';
1866
+ const agentState = dashboardState.agent?.state || '';
1509
1867
  return {
1510
1868
  authLabel: getDashboardAuthLabel(dashboardState.choice, dashboardState.loggedOut),
1511
1869
  connectedAt: dashboardState.connectedAt || '',
@@ -1517,10 +1875,11 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1517
1875
  statusLabel: dashboardState.loggedOut
1518
1876
  ? 'Signed out for next restart'
1519
1877
  : manager
1520
- ? 'Client starting'
1878
+ ? agentState === 'running' ? 'Agent running' : 'Client starting'
1521
1879
  : completed
1522
1880
  ? 'Finding Hub'
1523
- : 'Waiting for auth'
1881
+ : 'Waiting for auth',
1882
+ system: getDashboardSystemInfo(dashboardState)
1524
1883
  };
1525
1884
  };
1526
1885
 
@@ -1789,6 +2148,7 @@ async function chooseClientConnection(supabase, options = {}) {
1789
2148
  const connectionPage = await startConnectionChoiceServer(supabase, {
1790
2149
  authPort: options.authPort,
1791
2150
  autoGoogle: options.autoGoogle,
2151
+ engine: options.engine,
1792
2152
  slot: options.slot,
1793
2153
  startupArgs: options.startupArgs,
1794
2154
  savedSession: options.savedSession,
@@ -1944,6 +2304,7 @@ async function prepareLoginConnection(parsed) {
1944
2304
  choice = await chooseClientConnection(supabase, {
1945
2305
  authPort: parsed.authPort,
1946
2306
  autoGoogle: !savedSession?.access_token && !savedPin,
2307
+ engine: parsed.engine,
1947
2308
  slot: parsed.slot,
1948
2309
  startupArgs,
1949
2310
  savedSession,
@@ -2169,6 +2530,27 @@ function buildFastArgs(args, fakeThumbnail) {
2169
2530
  return forwarded;
2170
2531
  }
2171
2532
 
2533
+ function redactAgentArgs(args = []) {
2534
+ const redacted = [];
2535
+ const secretFlags = new Set(['--pair', '--token', '--key']);
2536
+ for (let index = 0; index < args.length; index += 1) {
2537
+ const arg = String(args[index] || '');
2538
+ const equalsIndex = arg.indexOf('=');
2539
+ if (equalsIndex > 0 && secretFlags.has(arg.slice(0, equalsIndex))) {
2540
+ redacted.push(`${arg.slice(0, equalsIndex)}=<hidden>`);
2541
+ continue;
2542
+ }
2543
+ redacted.push(arg);
2544
+ if (secretFlags.has(arg)) {
2545
+ if (index + 1 < args.length) {
2546
+ redacted.push('<hidden>');
2547
+ index += 1;
2548
+ }
2549
+ }
2550
+ }
2551
+ return redacted;
2552
+ }
2553
+
2172
2554
  function resolveFastLaunch(runtime) {
2173
2555
  if (!runtime) {
2174
2556
  return {
@@ -2217,12 +2599,15 @@ function resolveFastLaunch(runtime) {
2217
2599
  };
2218
2600
  }
2219
2601
 
2220
- function spawnAgent(command, args, env = process.env) {
2602
+ function spawnAgent(command, args, env = process.env, onStart = null) {
2221
2603
  const child = spawn(command, args, {
2222
2604
  env,
2223
2605
  stdio: 'inherit',
2224
2606
  windowsHide: true
2225
2607
  });
2608
+ if (typeof onStart === 'function') {
2609
+ onStart(child);
2610
+ }
2226
2611
 
2227
2612
  return new Promise(resolve => {
2228
2613
  child.once('error', error => {
@@ -2267,22 +2652,88 @@ async function main() {
2267
2652
  const prepared = await prepareLoginConnection(parsed);
2268
2653
  const fastRuntime = getFastRuntime();
2269
2654
  const useFast = shouldTryFast(prepared, fastRuntime);
2655
+ const updateAgentDashboard = (patch = {}) => {
2656
+ prepared.connectionPage?.update({
2657
+ agent: {
2658
+ requestedEngine: prepared.engine,
2659
+ state: 'launching',
2660
+ ...patch
2661
+ },
2662
+ message: patch.state === 'running'
2663
+ ? `LiveDesk client agent is running with ${patch.engine || 'agent'} mode.`
2664
+ : 'LiveDesk client agent is launching.'
2665
+ });
2666
+ };
2270
2667
  let result;
2271
2668
  if (useFast) {
2272
2669
  const fastArgs = buildFastArgs(prepared.forwarded, prepared.fakeThumbnail);
2273
2670
  const fastLaunch = resolveFastLaunch(fastRuntime);
2274
2671
  if (fastLaunch.ok) {
2275
- result = await spawnAgent(fastLaunch.command, [...fastLaunch.argsPrefix, ...fastArgs], buildFastEnvironment());
2672
+ const launchArgs = [...fastLaunch.argsPrefix, ...fastArgs];
2673
+ updateAgentDashboard({
2674
+ engine: 'fast',
2675
+ command: fastLaunch.command,
2676
+ args: redactAgentArgs(launchArgs),
2677
+ runtimeId: fastRuntime?.rid || '',
2678
+ state: 'launching'
2679
+ });
2680
+ result = await spawnAgent(fastLaunch.command, launchArgs, buildFastEnvironment(), child => {
2681
+ updateAgentDashboard({
2682
+ engine: 'fast',
2683
+ command: fastLaunch.command,
2684
+ args: redactAgentArgs(launchArgs),
2685
+ runtimeId: fastRuntime?.rid || '',
2686
+ pid: child.pid,
2687
+ startedAt: new Date().toISOString(),
2688
+ state: 'running'
2689
+ });
2690
+ });
2276
2691
  } else if (prepared.engine === 'fast') {
2277
2692
  console.error(`C# RemoteFast is unavailable: ${fastLaunch.reason}. Use --engine node to run the legacy Node agent.`);
2278
2693
  prepared.connectionPage?.close?.();
2279
2694
  process.exit(2);
2280
2695
  } else {
2281
2696
  console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
2282
- result = await spawnAgent(process.execPath, [nodeAgentPath, ...prepared.forwarded]);
2697
+ const launchArgs = [nodeAgentPath, ...prepared.forwarded];
2698
+ updateAgentDashboard({
2699
+ engine: 'node',
2700
+ command: process.execPath,
2701
+ args: redactAgentArgs(launchArgs),
2702
+ runtimeId: 'node-fallback',
2703
+ state: 'launching'
2704
+ });
2705
+ result = await spawnAgent(process.execPath, launchArgs, process.env, child => {
2706
+ updateAgentDashboard({
2707
+ engine: 'node',
2708
+ command: process.execPath,
2709
+ args: redactAgentArgs(launchArgs),
2710
+ runtimeId: 'node-fallback',
2711
+ pid: child.pid,
2712
+ startedAt: new Date().toISOString(),
2713
+ state: 'running'
2714
+ });
2715
+ });
2283
2716
  }
2284
2717
  } else {
2285
- result = await spawnAgent(process.execPath, [nodeAgentPath, ...prepared.forwarded]);
2718
+ const launchArgs = [nodeAgentPath, ...prepared.forwarded];
2719
+ updateAgentDashboard({
2720
+ engine: 'node',
2721
+ command: process.execPath,
2722
+ args: redactAgentArgs(launchArgs),
2723
+ runtimeId: 'node',
2724
+ state: 'launching'
2725
+ });
2726
+ result = await spawnAgent(process.execPath, launchArgs, process.env, child => {
2727
+ updateAgentDashboard({
2728
+ engine: 'node',
2729
+ command: process.execPath,
2730
+ args: redactAgentArgs(launchArgs),
2731
+ runtimeId: 'node',
2732
+ pid: child.pid,
2733
+ startedAt: new Date().toISOString(),
2734
+ state: 'running'
2735
+ });
2736
+ });
2286
2737
  }
2287
2738
  prepared.connectionPage?.close?.();
2288
2739
  if (result?.signal) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {