@livedesk/client 0.1.63 → 0.1.65

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.
@@ -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';
@@ -429,6 +429,10 @@ function writeSavedSessionToFile(session) {
429
429
  return true;
430
430
  }
431
431
 
432
+ function clearSavedSession() {
433
+ rmSync(CLIENT_AUTH_PATH, { force: true });
434
+ }
435
+
432
436
  async function createSupabaseClient() {
433
437
  const { createClient } = await import('@supabase/supabase-js');
434
438
  return createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
@@ -466,6 +470,14 @@ function getNestedErrorMessage(error) {
466
470
  return messages.join(' ');
467
471
  }
468
472
 
473
+ function isInvalidRefreshTokenError(error) {
474
+ const code = getNestedErrorCode(error).toLowerCase();
475
+ const message = getNestedErrorMessage(error);
476
+ return code === 'refresh_token_not_found'
477
+ || code === 'invalid_grant'
478
+ || /invalid refresh token|refresh token not found/i.test(message);
479
+ }
480
+
469
481
  function isTransientNetworkError(error) {
470
482
  const code = getNestedErrorCode(error).toUpperCase();
471
483
  if (['ENOTFOUND', 'EAI_AGAIN', 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENETUNREACH', 'EHOSTUNREACH'].includes(code)) {
@@ -483,8 +495,18 @@ function formatDiscoveryError(error) {
483
495
  }
484
496
 
485
497
  async function refreshSessionIfNeeded(supabase) {
486
- const { data: existing } = await supabase.auth.getSession();
487
- const session = existing?.session || readSavedSessionFromFile();
498
+ let existingSession = null;
499
+ try {
500
+ const { data: existing } = await supabase.auth.getSession();
501
+ existingSession = existing?.session || null;
502
+ } catch (error) {
503
+ if (isInvalidRefreshTokenError(error)) {
504
+ clearSavedSession();
505
+ return null;
506
+ }
507
+ throw error;
508
+ }
509
+ const session = existingSession || readSavedSessionFromFile();
488
510
  if (!session?.access_token) {
489
511
  return null;
490
512
  }
@@ -495,6 +517,10 @@ async function refreshSessionIfNeeded(supabase) {
495
517
  }
496
518
  const { data, error } = await supabase.auth.refreshSession(session);
497
519
  if (error) {
520
+ if (isInvalidRefreshTokenError(error)) {
521
+ clearSavedSession();
522
+ return null;
523
+ }
498
524
  throw error;
499
525
  }
500
526
  const refreshed = data?.session || session;
@@ -1035,16 +1061,148 @@ function getDashboardAuthLabel(choice, loggedOut = false) {
1035
1061
  return email ? `Google - ${email}` : 'Google signed in';
1036
1062
  }
1037
1063
 
1064
+ function formatDashboardBytes(value) {
1065
+ const bytes = Number(value || 0);
1066
+ if (!Number.isFinite(bytes) || bytes <= 0) {
1067
+ return '-';
1068
+ }
1069
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
1070
+ let next = bytes;
1071
+ let unitIndex = 0;
1072
+ while (next >= 1024 && unitIndex < units.length - 1) {
1073
+ next /= 1024;
1074
+ unitIndex += 1;
1075
+ }
1076
+ const precision = next >= 100 || unitIndex === 0 ? 0 : next >= 10 ? 1 : 2;
1077
+ return `${next.toFixed(precision)} ${units[unitIndex]}`;
1078
+ }
1079
+
1080
+ function formatDashboardDuration(seconds) {
1081
+ const totalSeconds = Math.max(0, Math.floor(Number(seconds || 0)));
1082
+ const days = Math.floor(totalSeconds / 86400);
1083
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
1084
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
1085
+ if (days > 0) {
1086
+ return `${days}d ${hours}h`;
1087
+ }
1088
+ if (hours > 0) {
1089
+ return `${hours}h ${minutes}m`;
1090
+ }
1091
+ return `${minutes}m`;
1092
+ }
1093
+
1094
+ function getDashboardNetworkInfo() {
1095
+ const rows = [];
1096
+ const interfaces = os.networkInterfaces();
1097
+ for (const [name, addresses] of Object.entries(interfaces)) {
1098
+ for (const address of addresses || []) {
1099
+ if (!address || address.internal) {
1100
+ continue;
1101
+ }
1102
+ rows.push({
1103
+ name,
1104
+ family: address.family,
1105
+ address: address.address,
1106
+ cidr: address.cidr || ''
1107
+ });
1108
+ }
1109
+ }
1110
+ return rows.slice(0, 8);
1111
+ }
1112
+
1113
+ function getDashboardDiskInfo() {
1114
+ try {
1115
+ const stats = statfsSync(os.homedir());
1116
+ const totalBytes = Number(stats.blocks || 0) * Number(stats.bsize || 0);
1117
+ const freeBytes = Number(stats.bavail || stats.bfree || 0) * Number(stats.bsize || 0);
1118
+ const usedBytes = Math.max(0, totalBytes - freeBytes);
1119
+ return {
1120
+ path: os.homedir(),
1121
+ total: formatDashboardBytes(totalBytes),
1122
+ free: formatDashboardBytes(freeBytes),
1123
+ used: formatDashboardBytes(usedBytes),
1124
+ usedPercent: totalBytes > 0 ? Math.round(usedBytes * 100 / totalBytes) : 0
1125
+ };
1126
+ } catch {
1127
+ return {
1128
+ path: os.homedir(),
1129
+ total: '-',
1130
+ free: '-',
1131
+ used: '-',
1132
+ usedPercent: 0
1133
+ };
1134
+ }
1135
+ }
1136
+
1137
+ function getDashboardUsername() {
1138
+ try {
1139
+ return os.userInfo().username || process.env.USERNAME || process.env.USER || '';
1140
+ } catch {
1141
+ return process.env.USERNAME || process.env.USER || '';
1142
+ }
1143
+ }
1144
+
1145
+ function getDashboardSystemInfo(state = {}) {
1146
+ const totalMem = os.totalmem();
1147
+ const freeMem = os.freemem();
1148
+ const usedMem = Math.max(0, totalMem - freeMem);
1149
+ const cpus = os.cpus();
1150
+ const load = os.loadavg().map(value => Number.isFinite(value) ? Number(value.toFixed(2)) : 0);
1151
+ const fastRuntime = getFastRuntime();
1152
+ const fastPackaged = fastRuntime
1153
+ ? (hasFastExecutable(fastRuntime) || hasFastDll(fastRuntime))
1154
+ : false;
1155
+ const agent = state.agent || {};
1156
+ return {
1157
+ hostname: os.hostname(),
1158
+ platform: os.platform(),
1159
+ release: os.release(),
1160
+ arch: os.arch(),
1161
+ type: os.type(),
1162
+ user: getDashboardUsername(),
1163
+ home: os.homedir(),
1164
+ cwd: process.cwd(),
1165
+ node: process.version,
1166
+ packageVersion: readPackageVersion(),
1167
+ pid: process.pid,
1168
+ processUptime: formatDashboardDuration(process.uptime()),
1169
+ systemUptime: formatDashboardDuration(os.uptime()),
1170
+ cpuModel: cpus[0]?.model || '-',
1171
+ cpuCores: cpus.length,
1172
+ loadAverage: load.join(' / '),
1173
+ memory: {
1174
+ total: formatDashboardBytes(totalMem),
1175
+ free: formatDashboardBytes(freeMem),
1176
+ used: formatDashboardBytes(usedMem),
1177
+ usedPercent: totalMem > 0 ? Math.round(usedMem * 100 / totalMem) : 0
1178
+ },
1179
+ disk: getDashboardDiskInfo(),
1180
+ network: getDashboardNetworkInfo(),
1181
+ runtime: {
1182
+ requestedEngine: agent.requestedEngine || '',
1183
+ activeEngine: agent.engine || 'pending',
1184
+ state: agent.state || 'waiting',
1185
+ pid: agent.pid || '',
1186
+ command: agent.command || '',
1187
+ args: Array.isArray(agent.args) ? agent.args.join(' ') : '',
1188
+ runtimeId: agent.runtimeId || fastRuntime?.rid || '',
1189
+ fastPackaged,
1190
+ startedAt: agent.startedAt || ''
1191
+ }
1192
+ };
1193
+ }
1194
+
1038
1195
  function renderConnectionDashboardPage(state = {}) {
1039
1196
  const choice = state.choice || null;
1040
1197
  const connectedAt = state.connectedAt || new Date().toISOString();
1041
1198
  const authLabel = getDashboardAuthLabel(choice, state.loggedOut);
1042
1199
  const manager = state.manager || (choice?.manager || '');
1200
+ const agentState = state.agent?.state || '';
1043
1201
  const slot = normalizeSlotNumber(state.slot) ? `Slot ${String(state.slot).padStart(3, '0')}` : 'First available';
1044
1202
  const statusLabel = state.loggedOut
1045
1203
  ? 'Signed out for next restart'
1046
1204
  : manager
1047
- ? 'Client starting'
1205
+ ? agentState === 'running' ? 'Agent running' : 'Client starting'
1048
1206
  : 'Finding Hub';
1049
1207
  const message = state.message || (manager
1050
1208
  ? 'The LiveDesk client is running from the terminal. Keep this tab open as the local client dashboard.'
@@ -1057,7 +1215,8 @@ function renderConnectionDashboardPage(state = {}) {
1057
1215
  slot,
1058
1216
  startup: Boolean(state.startup),
1059
1217
  completed: true,
1060
- statusLabel
1218
+ statusLabel,
1219
+ system: getDashboardSystemInfo(state)
1061
1220
  }).replaceAll('<', '\\u003c');
1062
1221
 
1063
1222
  return `<!doctype html>
@@ -1073,22 +1232,24 @@ function renderConnectionDashboardPage(state = {}) {
1073
1232
  body {
1074
1233
  min-height: 100vh;
1075
1234
  display: grid;
1076
- place-items: center;
1077
- padding: 28px;
1235
+ align-items: stretch;
1236
+ padding: 18px;
1078
1237
  background:
1079
- radial-gradient(circle at 78% 10%, rgba(203, 213, 225, 0.46), transparent 24%),
1238
+ radial-gradient(circle at 84% 0%, rgba(203, 213, 225, 0.36), transparent 26%),
1080
1239
  linear-gradient(180deg, #f8fafc, #eef2f7);
1081
1240
  color: #111827;
1082
1241
  }
1083
1242
  main {
1084
- width: min(920px, 100%);
1243
+ width: min(1280px, 100%);
1244
+ min-height: calc(100vh - 36px);
1245
+ margin: 0 auto;
1085
1246
  display: grid;
1086
- grid-template-columns: minmax(280px, 0.88fr) minmax(320px, 1.12fr);
1087
- overflow: hidden;
1247
+ grid-template-columns: minmax(286px, 0.74fr) minmax(640px, 1.26fr);
1088
1248
  border: 1px solid rgba(148, 163, 184, 0.28);
1089
- border-radius: 18px;
1249
+ border-radius: 20px;
1090
1250
  background: rgba(255, 255, 255, 0.92);
1091
1251
  box-shadow: 0 36px 110px rgba(15, 23, 42, 0.14);
1252
+ overflow: hidden;
1092
1253
  }
1093
1254
  .hero {
1094
1255
  display: grid;
@@ -1144,10 +1305,14 @@ function renderConnectionDashboardPage(state = {}) {
1144
1305
  }
1145
1306
  .panel {
1146
1307
  display: grid;
1147
- gap: 16px;
1148
- padding: 34px;
1308
+ align-content: start;
1309
+ gap: 14px;
1310
+ padding: 24px;
1311
+ background:
1312
+ linear-gradient(180deg, rgba(248, 250, 252, 0.78), rgba(241, 245, 249, 0.72)),
1313
+ #f8fafc;
1149
1314
  }
1150
- .status-card {
1315
+ .status-card, .section-card {
1151
1316
  display: grid;
1152
1317
  gap: 14px;
1153
1318
  padding: 18px;
@@ -1158,6 +1323,7 @@ function renderConnectionDashboardPage(state = {}) {
1158
1323
  0 20px 50px rgba(15, 23, 42, 0.09),
1159
1324
  0 1px 0 rgba(255, 255, 255, 0.9) inset;
1160
1325
  }
1326
+ .section-card { gap: 12px; }
1161
1327
  .status-head {
1162
1328
  display: flex;
1163
1329
  align-items: center;
@@ -1191,6 +1357,17 @@ function renderConnectionDashboardPage(state = {}) {
1191
1357
  font-size: 20px;
1192
1358
  line-height: 1.18;
1193
1359
  }
1360
+ .section-title {
1361
+ display: flex;
1362
+ align-items: center;
1363
+ justify-content: space-between;
1364
+ gap: 12px;
1365
+ color: #0f172a;
1366
+ font-size: 12px;
1367
+ font-weight: 850;
1368
+ letter-spacing: 0.04em;
1369
+ text-transform: uppercase;
1370
+ }
1194
1371
  dl {
1195
1372
  display: grid;
1196
1373
  grid-template-columns: 118px minmax(0, 1fr);
@@ -1213,6 +1390,91 @@ function renderConnectionDashboardPage(state = {}) {
1213
1390
  text-overflow: ellipsis;
1214
1391
  white-space: nowrap;
1215
1392
  }
1393
+ .hero-metrics, .metric-grid {
1394
+ display: grid;
1395
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1396
+ gap: 10px;
1397
+ }
1398
+ .hero-metric, .metric-card {
1399
+ display: grid;
1400
+ gap: 6px;
1401
+ min-height: 68px;
1402
+ padding: 12px;
1403
+ border: 1px solid rgba(148, 163, 184, 0.24);
1404
+ border-radius: 12px;
1405
+ background: rgba(255, 255, 255, 0.78);
1406
+ box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
1407
+ }
1408
+ .hero-metric span, .metric-card span {
1409
+ color: #64748b;
1410
+ font-size: 11px;
1411
+ font-weight: 780;
1412
+ letter-spacing: 0.04em;
1413
+ text-transform: uppercase;
1414
+ }
1415
+ .hero-metric strong, .metric-card strong {
1416
+ min-width: 0;
1417
+ overflow: hidden;
1418
+ color: #0f172a;
1419
+ font-size: 18px;
1420
+ font-weight: 780;
1421
+ text-overflow: ellipsis;
1422
+ white-space: nowrap;
1423
+ }
1424
+ .progress {
1425
+ width: 100%;
1426
+ height: 4px;
1427
+ overflow: hidden;
1428
+ border-radius: 999px;
1429
+ background: #e2e8f0;
1430
+ }
1431
+ .progress i {
1432
+ display: block;
1433
+ width: 0%;
1434
+ height: 100%;
1435
+ border-radius: inherit;
1436
+ background: #111827;
1437
+ transition: width 180ms ease;
1438
+ }
1439
+ .info-grid {
1440
+ display: grid;
1441
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1442
+ gap: 9px 14px;
1443
+ }
1444
+ .info-item {
1445
+ min-width: 0;
1446
+ display: grid;
1447
+ gap: 3px;
1448
+ }
1449
+ .info-item span, .network-row span {
1450
+ color: #64748b;
1451
+ font-size: 11px;
1452
+ font-weight: 760;
1453
+ }
1454
+ .info-item strong, .network-row strong {
1455
+ min-width: 0;
1456
+ overflow: hidden;
1457
+ color: #111827;
1458
+ font-size: 13px;
1459
+ font-weight: 720;
1460
+ text-overflow: ellipsis;
1461
+ white-space: nowrap;
1462
+ }
1463
+ .network-list {
1464
+ display: grid;
1465
+ gap: 7px;
1466
+ }
1467
+ .network-row {
1468
+ display: grid;
1469
+ grid-template-columns: minmax(88px, 0.45fr) minmax(0, 1fr);
1470
+ gap: 12px;
1471
+ min-height: 32px;
1472
+ align-items: center;
1473
+ padding: 8px 10px;
1474
+ border: 1px solid rgba(148, 163, 184, 0.24);
1475
+ border-radius: 9px;
1476
+ background: #f8fafc;
1477
+ }
1216
1478
  .actions {
1217
1479
  display: grid;
1218
1480
  grid-template-columns: 1fr 1fr;
@@ -1256,6 +1518,7 @@ function renderConnectionDashboardPage(state = {}) {
1256
1518
  .hero, .panel { padding: 24px; }
1257
1519
  h1 { font-size: 28px; }
1258
1520
  .actions { grid-template-columns: 1fr; }
1521
+ .hero-metrics, .metric-grid, .info-grid { grid-template-columns: 1fr; }
1259
1522
  }
1260
1523
  </style>
1261
1524
  </head>
@@ -1274,6 +1537,26 @@ function renderConnectionDashboardPage(state = {}) {
1274
1537
  <h1>Client is ready</h1>
1275
1538
  <p id="message">${escapeHtml(message)}</p>
1276
1539
  </div>
1540
+ <div class="hero-metrics">
1541
+ <div class="hero-metric">
1542
+ <span>Host</span>
1543
+ <strong id="hero-host">-</strong>
1544
+ </div>
1545
+ <div class="hero-metric">
1546
+ <span>Engine</span>
1547
+ <strong id="hero-engine">pending</strong>
1548
+ </div>
1549
+ <div class="hero-metric">
1550
+ <span>Memory</span>
1551
+ <strong id="hero-memory">-</strong>
1552
+ <div class="progress"><i id="memory-bar"></i></div>
1553
+ </div>
1554
+ <div class="hero-metric">
1555
+ <span>Disk</span>
1556
+ <strong id="hero-disk">-</strong>
1557
+ <div class="progress"><i id="disk-bar"></i></div>
1558
+ </div>
1559
+ </div>
1277
1560
  <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
1561
  </section>
1279
1562
  <section class="panel">
@@ -1290,6 +1573,47 @@ function renderConnectionDashboardPage(state = {}) {
1290
1573
  <dt>Started</dt><dd id="connected-at">${escapeHtml(connectedAt)}</dd>
1291
1574
  </dl>
1292
1575
  </article>
1576
+ <article class="section-card">
1577
+ <div class="section-title">System</div>
1578
+ <div class="metric-grid">
1579
+ <div class="metric-card"><span>CPU</span><strong id="cpu-model">-</strong></div>
1580
+ <div class="metric-card"><span>Cores</span><strong id="cpu-cores">-</strong></div>
1581
+ <div class="metric-card"><span>OS</span><strong id="os-line">-</strong></div>
1582
+ <div class="metric-card"><span>Uptime</span><strong id="system-uptime">-</strong></div>
1583
+ <div class="metric-card"><span>Load avg</span><strong id="load-average">-</strong></div>
1584
+ <div class="metric-card"><span>Architecture</span><strong id="arch-line">-</strong></div>
1585
+ </div>
1586
+ </article>
1587
+ <article class="section-card">
1588
+ <div class="section-title">Runtime</div>
1589
+ <div class="info-grid">
1590
+ <div class="info-item"><span>Agent state</span><strong id="agent-state">waiting</strong></div>
1591
+ <div class="info-item"><span>Agent PID</span><strong id="agent-pid">-</strong></div>
1592
+ <div class="info-item"><span>Runtime</span><strong id="runtime-id">-</strong></div>
1593
+ <div class="info-item"><span>Fast package</span><strong id="fast-packaged">-</strong></div>
1594
+ <div class="info-item"><span>Client PID</span><strong id="client-pid">-</strong></div>
1595
+ <div class="info-item"><span>Node</span><strong id="node-version">-</strong></div>
1596
+ <div class="info-item"><span>Package</span><strong id="package-version">-</strong></div>
1597
+ <div class="info-item"><span>Process uptime</span><strong id="process-uptime">-</strong></div>
1598
+ <div class="info-item"><span>Agent started</span><strong id="agent-started">-</strong></div>
1599
+ </div>
1600
+ </article>
1601
+ <article class="section-card">
1602
+ <div class="section-title">Storage & Paths</div>
1603
+ <div class="info-grid">
1604
+ <div class="info-item"><span>Disk used</span><strong id="disk-used">-</strong></div>
1605
+ <div class="info-item"><span>Disk free</span><strong id="disk-free">-</strong></div>
1606
+ <div class="info-item"><span>User</span><strong id="user-name">-</strong></div>
1607
+ <div class="info-item"><span>Home</span><strong id="home-path">-</strong></div>
1608
+ <div class="info-item"><span>Working dir</span><strong id="cwd-path">-</strong></div>
1609
+ <div class="info-item"><span>Agent command</span><strong id="agent-command">-</strong></div>
1610
+ <div class="info-item"><span>Agent args</span><strong id="agent-args">-</strong></div>
1611
+ </div>
1612
+ </article>
1613
+ <article class="section-card">
1614
+ <div class="section-title">Network</div>
1615
+ <div id="network-list" class="network-list"></div>
1616
+ </article>
1293
1617
  <div class="actions">
1294
1618
  <a class="button secondary" href="/">Refresh dashboard</a>
1295
1619
  <form method="post" action="/logout">
@@ -1305,7 +1629,33 @@ function renderConnectionDashboardPage(state = {}) {
1305
1629
  const node = document.getElementById(id);
1306
1630
  if (node) node.textContent = value || '';
1307
1631
  }
1632
+ function setBar(id, value) {
1633
+ const node = document.getElementById(id);
1634
+ if (!node) return;
1635
+ const percent = Math.max(0, Math.min(100, Number(value) || 0));
1636
+ node.style.width = percent + '%';
1637
+ }
1638
+ function renderNetworkRows(items) {
1639
+ const list = document.getElementById('network-list');
1640
+ if (!list) return;
1641
+ list.textContent = '';
1642
+ const rows = Array.isArray(items) && items.length > 0 ? items : [{ name: 'Network', address: 'not reported', family: '' }];
1643
+ for (const item of rows) {
1644
+ const row = document.createElement('div');
1645
+ row.className = 'network-row';
1646
+ const name = document.createElement('span');
1647
+ name.textContent = [item.name, item.family].filter(Boolean).join(' / ');
1648
+ const address = document.createElement('strong');
1649
+ address.textContent = item.cidr || item.address || 'not reported';
1650
+ row.append(name, address);
1651
+ list.append(row);
1652
+ }
1653
+ }
1308
1654
  function applyState(state) {
1655
+ const system = state.system || {};
1656
+ const memory = system.memory || {};
1657
+ const disk = system.disk || {};
1658
+ const runtime = system.runtime || {};
1309
1659
  setText('status', state.statusLabel || initialState.statusLabel);
1310
1660
  setText('auth', state.authLabel || initialState.authLabel);
1311
1661
  setText('manager', state.manager || 'waiting');
@@ -1313,6 +1663,35 @@ function renderConnectionDashboardPage(state = {}) {
1313
1663
  setText('startup', state.startup ? 'enabled' : 'manual');
1314
1664
  setText('connected-at', state.connectedAt || initialState.connectedAt);
1315
1665
  setText('message', state.message || initialState.message);
1666
+ setText('hero-host', system.hostname || '-');
1667
+ setText('hero-engine', runtime.activeEngine || runtime.state || 'pending');
1668
+ setText('hero-memory', memory.used && memory.total ? memory.used + ' / ' + memory.total : '-');
1669
+ setText('hero-disk', disk.usedPercent ? disk.usedPercent + '% used' : disk.free ? disk.free + ' free' : '-');
1670
+ setBar('memory-bar', memory.usedPercent);
1671
+ setBar('disk-bar', disk.usedPercent);
1672
+ setText('cpu-model', system.cpuModel || '-');
1673
+ setText('cpu-cores', system.cpuCores ? String(system.cpuCores) : '-');
1674
+ setText('os-line', [system.type || system.platform, system.release, system.arch].filter(Boolean).join(' '));
1675
+ setText('system-uptime', system.systemUptime || '-');
1676
+ setText('load-average', system.loadAverage || '-');
1677
+ setText('arch-line', [system.platform, system.arch].filter(Boolean).join(' / '));
1678
+ setText('agent-state', runtime.state || 'waiting');
1679
+ setText('agent-pid', runtime.pid ? String(runtime.pid) : '-');
1680
+ setText('runtime-id', runtime.runtimeId || runtime.requestedEngine || '-');
1681
+ setText('fast-packaged', runtime.fastPackaged ? 'available' : 'missing');
1682
+ setText('client-pid', system.pid ? String(system.pid) : '-');
1683
+ setText('node-version', system.node || '-');
1684
+ setText('package-version', system.packageVersion || '-');
1685
+ setText('process-uptime', system.processUptime || '-');
1686
+ setText('agent-started', runtime.startedAt || '-');
1687
+ setText('disk-used', disk.used && disk.total ? disk.used + ' / ' + disk.total : '-');
1688
+ setText('disk-free', disk.free || '-');
1689
+ setText('user-name', system.user || '-');
1690
+ setText('home-path', system.home || '-');
1691
+ setText('cwd-path', system.cwd || '-');
1692
+ setText('agent-command', runtime.command || '-');
1693
+ setText('agent-args', runtime.args || '-');
1694
+ renderNetworkRows(system.network);
1316
1695
  }
1317
1696
  applyState(initialState);
1318
1697
  if (location.pathname === '/callback') {
@@ -1458,6 +1837,10 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1458
1837
  let completedTitle = 'LiveDesk connection complete';
1459
1838
  let completedMessage = 'The LiveDesk client is starting automatically.';
1460
1839
  const dashboardState = {
1840
+ agent: {
1841
+ requestedEngine: options.engine || '',
1842
+ state: 'waiting'
1843
+ },
1461
1844
  choice: null,
1462
1845
  connectedAt: '',
1463
1846
  endpointCandidates: [],
@@ -1506,6 +1889,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1506
1889
  });
1507
1890
  const getDashboardApiState = () => {
1508
1891
  const manager = dashboardState.manager || dashboardState.choice?.manager || '';
1892
+ const agentState = dashboardState.agent?.state || '';
1509
1893
  return {
1510
1894
  authLabel: getDashboardAuthLabel(dashboardState.choice, dashboardState.loggedOut),
1511
1895
  connectedAt: dashboardState.connectedAt || '',
@@ -1517,10 +1901,11 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1517
1901
  statusLabel: dashboardState.loggedOut
1518
1902
  ? 'Signed out for next restart'
1519
1903
  : manager
1520
- ? 'Client starting'
1904
+ ? agentState === 'running' ? 'Agent running' : 'Client starting'
1521
1905
  : completed
1522
1906
  ? 'Finding Hub'
1523
- : 'Waiting for auth'
1907
+ : 'Waiting for auth',
1908
+ system: getDashboardSystemInfo(dashboardState)
1524
1909
  };
1525
1910
  };
1526
1911
 
@@ -1588,7 +1973,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1588
1973
  await supabase.auth.signOut();
1589
1974
  } catch {
1590
1975
  }
1591
- rmSync(CLIENT_AUTH_PATH, { force: true });
1976
+ clearSavedSession();
1592
1977
  clearSavedPin();
1593
1978
  dashboardState.choice = null;
1594
1979
  dashboardState.loggedOut = true;
@@ -1621,11 +2006,22 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1621
2006
 
1622
2007
  if (requestUrl.pathname === '/google') {
1623
2008
  pendingStartup = normalizeStartupChoice(requestUrl.searchParams.get('startup'), pendingStartup);
1624
- const { data: existing } = await supabase.auth.getSession();
1625
- if (existing?.session?.access_token) {
1626
- writeSavedSessionToFile(existing.session);
2009
+ let existingSession = null;
2010
+ try {
2011
+ const { data: existing } = await supabase.auth.getSession();
2012
+ existingSession = existing?.session || null;
2013
+ } catch (err) {
2014
+ if (isInvalidRefreshTokenError(err)) {
2015
+ clearSavedSession();
2016
+ } else {
2017
+ handleError(res, err);
2018
+ return;
2019
+ }
2020
+ }
2021
+ if (existingSession?.access_token) {
2022
+ writeSavedSessionToFile(existingSession);
1627
2023
  applyStartupPreference(pendingStartup, startupArgs);
1628
- const choice = { type: 'google', session: existing.session };
2024
+ const choice = { type: 'google', session: existingSession };
1629
2025
  complete(choice, 'LiveDesk client dashboard', 'Signed in. The client is starting automatically.');
1630
2026
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1631
2027
  res.end(renderDashboard());
@@ -1789,6 +2185,7 @@ async function chooseClientConnection(supabase, options = {}) {
1789
2185
  const connectionPage = await startConnectionChoiceServer(supabase, {
1790
2186
  authPort: options.authPort,
1791
2187
  autoGoogle: options.autoGoogle,
2188
+ engine: options.engine,
1792
2189
  slot: options.slot,
1793
2190
  startupArgs: options.startupArgs,
1794
2191
  savedSession: options.savedSession,
@@ -1944,6 +2341,7 @@ async function prepareLoginConnection(parsed) {
1944
2341
  choice = await chooseClientConnection(supabase, {
1945
2342
  authPort: parsed.authPort,
1946
2343
  autoGoogle: !savedSession?.access_token && !savedPin,
2344
+ engine: parsed.engine,
1947
2345
  slot: parsed.slot,
1948
2346
  startupArgs,
1949
2347
  savedSession,
@@ -2169,6 +2567,27 @@ function buildFastArgs(args, fakeThumbnail) {
2169
2567
  return forwarded;
2170
2568
  }
2171
2569
 
2570
+ function redactAgentArgs(args = []) {
2571
+ const redacted = [];
2572
+ const secretFlags = new Set(['--pair', '--token', '--key']);
2573
+ for (let index = 0; index < args.length; index += 1) {
2574
+ const arg = String(args[index] || '');
2575
+ const equalsIndex = arg.indexOf('=');
2576
+ if (equalsIndex > 0 && secretFlags.has(arg.slice(0, equalsIndex))) {
2577
+ redacted.push(`${arg.slice(0, equalsIndex)}=<hidden>`);
2578
+ continue;
2579
+ }
2580
+ redacted.push(arg);
2581
+ if (secretFlags.has(arg)) {
2582
+ if (index + 1 < args.length) {
2583
+ redacted.push('<hidden>');
2584
+ index += 1;
2585
+ }
2586
+ }
2587
+ }
2588
+ return redacted;
2589
+ }
2590
+
2172
2591
  function resolveFastLaunch(runtime) {
2173
2592
  if (!runtime) {
2174
2593
  return {
@@ -2217,12 +2636,15 @@ function resolveFastLaunch(runtime) {
2217
2636
  };
2218
2637
  }
2219
2638
 
2220
- function spawnAgent(command, args, env = process.env) {
2639
+ function spawnAgent(command, args, env = process.env, onStart = null) {
2221
2640
  const child = spawn(command, args, {
2222
2641
  env,
2223
2642
  stdio: 'inherit',
2224
2643
  windowsHide: true
2225
2644
  });
2645
+ if (typeof onStart === 'function') {
2646
+ onStart(child);
2647
+ }
2226
2648
 
2227
2649
  return new Promise(resolve => {
2228
2650
  child.once('error', error => {
@@ -2267,22 +2689,88 @@ async function main() {
2267
2689
  const prepared = await prepareLoginConnection(parsed);
2268
2690
  const fastRuntime = getFastRuntime();
2269
2691
  const useFast = shouldTryFast(prepared, fastRuntime);
2692
+ const updateAgentDashboard = (patch = {}) => {
2693
+ prepared.connectionPage?.update({
2694
+ agent: {
2695
+ requestedEngine: prepared.engine,
2696
+ state: 'launching',
2697
+ ...patch
2698
+ },
2699
+ message: patch.state === 'running'
2700
+ ? `LiveDesk client agent is running with ${patch.engine || 'agent'} mode.`
2701
+ : 'LiveDesk client agent is launching.'
2702
+ });
2703
+ };
2270
2704
  let result;
2271
2705
  if (useFast) {
2272
2706
  const fastArgs = buildFastArgs(prepared.forwarded, prepared.fakeThumbnail);
2273
2707
  const fastLaunch = resolveFastLaunch(fastRuntime);
2274
2708
  if (fastLaunch.ok) {
2275
- result = await spawnAgent(fastLaunch.command, [...fastLaunch.argsPrefix, ...fastArgs], buildFastEnvironment());
2709
+ const launchArgs = [...fastLaunch.argsPrefix, ...fastArgs];
2710
+ updateAgentDashboard({
2711
+ engine: 'fast',
2712
+ command: fastLaunch.command,
2713
+ args: redactAgentArgs(launchArgs),
2714
+ runtimeId: fastRuntime?.rid || '',
2715
+ state: 'launching'
2716
+ });
2717
+ result = await spawnAgent(fastLaunch.command, launchArgs, buildFastEnvironment(), child => {
2718
+ updateAgentDashboard({
2719
+ engine: 'fast',
2720
+ command: fastLaunch.command,
2721
+ args: redactAgentArgs(launchArgs),
2722
+ runtimeId: fastRuntime?.rid || '',
2723
+ pid: child.pid,
2724
+ startedAt: new Date().toISOString(),
2725
+ state: 'running'
2726
+ });
2727
+ });
2276
2728
  } else if (prepared.engine === 'fast') {
2277
2729
  console.error(`C# RemoteFast is unavailable: ${fastLaunch.reason}. Use --engine node to run the legacy Node agent.`);
2278
2730
  prepared.connectionPage?.close?.();
2279
2731
  process.exit(2);
2280
2732
  } else {
2281
2733
  console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
2282
- result = await spawnAgent(process.execPath, [nodeAgentPath, ...prepared.forwarded]);
2734
+ const launchArgs = [nodeAgentPath, ...prepared.forwarded];
2735
+ updateAgentDashboard({
2736
+ engine: 'node',
2737
+ command: process.execPath,
2738
+ args: redactAgentArgs(launchArgs),
2739
+ runtimeId: 'node-fallback',
2740
+ state: 'launching'
2741
+ });
2742
+ result = await spawnAgent(process.execPath, launchArgs, process.env, child => {
2743
+ updateAgentDashboard({
2744
+ engine: 'node',
2745
+ command: process.execPath,
2746
+ args: redactAgentArgs(launchArgs),
2747
+ runtimeId: 'node-fallback',
2748
+ pid: child.pid,
2749
+ startedAt: new Date().toISOString(),
2750
+ state: 'running'
2751
+ });
2752
+ });
2283
2753
  }
2284
2754
  } else {
2285
- result = await spawnAgent(process.execPath, [nodeAgentPath, ...prepared.forwarded]);
2755
+ const launchArgs = [nodeAgentPath, ...prepared.forwarded];
2756
+ updateAgentDashboard({
2757
+ engine: 'node',
2758
+ command: process.execPath,
2759
+ args: redactAgentArgs(launchArgs),
2760
+ runtimeId: 'node',
2761
+ state: 'launching'
2762
+ });
2763
+ result = await spawnAgent(process.execPath, launchArgs, process.env, child => {
2764
+ updateAgentDashboard({
2765
+ engine: 'node',
2766
+ command: process.execPath,
2767
+ args: redactAgentArgs(launchArgs),
2768
+ runtimeId: 'node',
2769
+ pid: child.pid,
2770
+ startedAt: new Date().toISOString(),
2771
+ state: 'running'
2772
+ });
2773
+ });
2286
2774
  }
2287
2775
  prepared.connectionPage?.close?.();
2288
2776
  if (result?.signal) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {