livedesk 0.1.681 → 0.1.683

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,12 +1,53 @@
1
+ const { execFileSync } = require('node:child_process');
1
2
  const { chmodSync, existsSync, statSync } = require('node:fs');
2
3
  const { dirname, join } = require('node:path');
3
4
 
5
+ const MACOS_SCREEN_PERMISSION_HELPER = 'vuvodesk-macos-screen-permission';
6
+
7
+ function buildMacosScreenPermissionHelper(context) {
8
+ const sourcePath = join(__dirname, 'macos-screen-recording-permission.swift');
9
+ const resourcesRoot = join(
10
+ context.appOutDir,
11
+ `${context.packager.appInfo.productFilename}.app`,
12
+ 'Contents',
13
+ 'Resources'
14
+ );
15
+ const outputPath = join(resourcesRoot, MACOS_SCREEN_PERMISSION_HELPER);
16
+ const target = context.arch === 1 || context.arch === 'x64'
17
+ ? 'x86_64-apple-macos10.15'
18
+ : context.arch === 3 || context.arch === 'arm64'
19
+ ? 'arm64-apple-macos11.0'
20
+ : '';
21
+ if (!target) {
22
+ throw new Error(`Unsupported macOS permission-helper architecture: ${context.arch}.`);
23
+ }
24
+ execFileSync('/usr/bin/xcrun', [
25
+ 'swiftc',
26
+ '-parse-as-library',
27
+ '-O',
28
+ '-target',
29
+ target,
30
+ '-framework',
31
+ 'CoreGraphics',
32
+ sourcePath,
33
+ '-o',
34
+ outputPath
35
+ ], { stdio: 'inherit' });
36
+ chmodSync(outputPath, 0o755);
37
+ const executableMode = statSync(outputPath).mode & 0o777;
38
+ if ((executableMode & 0o111) !== 0o111) {
39
+ throw new Error(`Packaged macOS permission helper must be executable; mode=${executableMode.toString(8)}`);
40
+ }
41
+ }
42
+
4
43
  module.exports = async function afterPack(context) {
5
44
  const platform = String(context.electronPlatformName || '');
6
45
  if (platform !== 'linux' && platform !== 'darwin') {
7
46
  return;
8
47
  }
9
48
 
49
+ if (platform === 'darwin') buildMacosScreenPermissionHelper(context);
50
+
10
51
  const unpackedRoot = platform === 'darwin'
11
52
  ? join(
12
53
  context.appOutDir,
@@ -27,7 +27,7 @@ function claimOnce(path) {
27
27
  }
28
28
 
29
29
  export function createMacosPermissionOnboardingOwner({
30
- desktopCapturer,
30
+ requestScreenRecording,
31
31
  systemPreferences,
32
32
  stateRoot,
33
33
  runtimeStateDir,
@@ -59,6 +59,19 @@ export function createMacosPermissionOnboardingOwner({
59
59
  'macos-screen-recording-requested-desktop-v1'
60
60
  );
61
61
  let primePromise = null;
62
+ let nativeMarkerClaimed = false;
63
+ let nativeMarkerError = '';
64
+
65
+ function claimNativePromptOwner() {
66
+ if (!supported || nativeMarkerClaimed) return nativeMarkerClaimed;
67
+ try {
68
+ claimOnce(nativeDesktopScreenMarkerPath);
69
+ nativeMarkerClaimed = true;
70
+ } catch {
71
+ nativeMarkerError = 'native-marker-unavailable';
72
+ }
73
+ return nativeMarkerClaimed;
74
+ }
62
75
 
63
76
  function readScreenStatus() {
64
77
  try {
@@ -99,12 +112,8 @@ export function createMacosPermissionOnboardingOwner({
99
112
  return result;
100
113
  }
101
114
 
102
- let error = '';
103
- try {
104
- claimOnce(nativeDesktopScreenMarkerPath);
105
- } catch {
106
- error = 'native-marker-unavailable';
107
- }
115
+ claimNativePromptOwner();
116
+ let error = nativeMarkerError;
108
117
 
109
118
  const screenBefore = readScreenStatus();
110
119
  let screenRequested = false;
@@ -116,13 +125,10 @@ export function createMacosPermissionOnboardingOwner({
116
125
  try {
117
126
  if (claimOnce(screenMarkerPath)) {
118
127
  screenRequested = true;
119
- // A zero-sized thumbnail asks macOS for the signed app permission
120
- // without retaining a screen image or starting a capture stream.
121
- await desktopCapturer.getSources({
122
- types: ['screen'],
123
- thumbnailSize: { width: 0, height: 0 },
124
- fetchWindowIcons: false
125
- });
128
+ if (typeof requestScreenRecording !== 'function') {
129
+ throw new Error('macos-screen-requester-unavailable');
130
+ }
131
+ await requestScreenRecording();
126
132
  }
127
133
  } catch {
128
134
  error ||= 'screen-request-failed';
@@ -164,6 +170,7 @@ export function createMacosPermissionOnboardingOwner({
164
170
  }
165
171
 
166
172
  return {
173
+ claimNativePromptOwner,
167
174
  prime,
168
175
  supported,
169
176
  screenMarkerPath,
@@ -0,0 +1,13 @@
1
+ import CoreGraphics
2
+ import Darwin
3
+
4
+ @main
5
+ struct VuvoDeskScreenRecordingPermission {
6
+ static func main() {
7
+ if CGPreflightScreenCaptureAccess() {
8
+ exit(0)
9
+ }
10
+
11
+ exit(CGRequestScreenCaptureAccess() ? 0 : 2)
12
+ }
13
+ }
package/electron/main.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { app, autoUpdater as electronAutoUpdater, BrowserWindow, clipboard, desktopCapturer, dialog, ipcMain, Menu, nativeImage, powerMonitor, safeStorage, shell, systemPreferences, Tray } from 'electron';
1
+ import { app, autoUpdater as electronAutoUpdater, BrowserWindow, clipboard, dialog, ipcMain, Menu, nativeImage, powerMonitor, safeStorage, shell, systemPreferences, Tray } from 'electron';
2
2
  import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
@@ -95,7 +95,9 @@ const logDir = join(stateRoot, 'logs');
95
95
  const logPath = join(logDir, 'desktop.log');
96
96
  const authSessionPath = join(stateRoot, 'auth-session.json');
97
97
  const authSessionBackupPath = join(stateRoot, 'auth-session.backup.json');
98
- const runtimeStateDir = process.env.LIVEDESK_STATE_DIR || join(homedir(), '.livedesk');
98
+ const runtimeStateDir = process.env.LIVEDESK_STATE_DIR || join(homedir(), '.livedesk');
99
+ const MACOS_SCREEN_PERMISSION_HELPER = 'vuvodesk-macos-screen-permission';
100
+ const MACOS_SCREEN_PERMISSION_TIMEOUT_MS = 120000;
99
101
  try { app.setPath('userData', stateRoot); } catch { /* Electron may reject late path changes in embedded test hosts. */ }
100
102
  const appPath = app.getAppPath();
101
103
  const unpackedAppPath = appPath.replace(/app\.asar([\\/]|$)/, 'app.asar.unpacked$1');
@@ -180,6 +182,58 @@ let lastKnownDesktopSession = null;
180
182
  let desktopClipboardOwner = null;
181
183
  let runtimeWindowLoadOwner = null;
182
184
  let desktopAutoStartOwner = null;
185
+ let macosPermissionOnboardingOwner = null;
186
+ let macosScreenPermissionChild = null;
187
+
188
+ function requestMacosScreenRecordingAccess() {
189
+ if (process.platform !== 'darwin' || !app.isPackaged) return Promise.resolve(false);
190
+ const helperPath = join(process.resourcesPath, MACOS_SCREEN_PERMISSION_HELPER);
191
+ if (!existsSync(helperPath)) {
192
+ return Promise.reject(new Error('macos-screen-permission-helper-unavailable'));
193
+ }
194
+ if (macosScreenPermissionChild) {
195
+ return Promise.reject(new Error('macos-screen-permission-helper-already-running'));
196
+ }
197
+
198
+ return new Promise((resolvePermission, rejectPermission) => {
199
+ const child = spawn(helperPath, [], {
200
+ stdio: ['ignore', 'ignore', 'ignore'],
201
+ windowsHide: true
202
+ });
203
+ macosScreenPermissionChild = child;
204
+ let settled = false;
205
+ const finish = (callback, value) => {
206
+ if (settled) return;
207
+ settled = true;
208
+ clearTimeout(timeout);
209
+ if (macosScreenPermissionChild === child) macosScreenPermissionChild = null;
210
+ callback(value);
211
+ };
212
+ const timeout = setTimeout(() => {
213
+ try { child.kill('SIGTERM'); } catch { /* already terminal */ }
214
+ finish(rejectPermission, new Error('macos-screen-permission-helper-timeout'));
215
+ }, MACOS_SCREEN_PERMISSION_TIMEOUT_MS);
216
+ timeout.unref?.();
217
+ child.once('error', error => finish(rejectPermission, error));
218
+ child.once('exit', (code, signal) => {
219
+ if (code === 0 || code === 2) {
220
+ finish(resolvePermission, code === 0);
221
+ return;
222
+ }
223
+ finish(
224
+ rejectPermission,
225
+ new Error(`macos-screen-permission-helper-exit-${code ?? 'signal'}-${signal || 'none'}`)
226
+ );
227
+ });
228
+ });
229
+ }
230
+
231
+ function stopMacosScreenPermissionRequest() {
232
+ const child = macosScreenPermissionChild;
233
+ macosScreenPermissionChild = null;
234
+ if (!child || child.killed) return;
235
+ try { child.kill('SIGTERM'); } catch { /* already terminal */ }
236
+ }
183
237
  const desktopAuthInvalidationGuard = createDesktopAuthInvalidationGuard();
184
238
  const installerQuitGate = createInstallerQuitGate({
185
239
  requestQuit: () => app.quit()
@@ -1190,10 +1244,12 @@ function showWindow() {
1190
1244
  runtimeWindowLoadOwner?.start('window-show');
1191
1245
  if (mainWindow.isMinimized()) mainWindow.restore();
1192
1246
  mainWindow.show();
1193
- mainWindow.focus();
1194
- if (!mainWindow.webContents.isDestroyed()) mainWindow.webContents.invalidate();
1195
- requestDesktopResumeAuthRecovery('window-show');
1196
- }
1247
+ if (process.platform === 'darwin') app.focus({ steal: true });
1248
+ mainWindow.focus();
1249
+ if (mainWindow.isFocused()) void macosPermissionOnboardingOwner?.prime();
1250
+ if (!mainWindow.webContents.isDestroyed()) mainWindow.webContents.invalidate();
1251
+ requestDesktopResumeAuthRecovery('window-show');
1252
+ }
1197
1253
 
1198
1254
  function createMainWindow({ showOnInitialLoad = true } = {}) {
1199
1255
  const iconPath = appResource('electron', 'icon.png');
@@ -1256,7 +1312,10 @@ function createMainWindow({ showOnInitialLoad = true } = {}) {
1256
1312
  event.preventDefault();
1257
1313
  if (!window.isDestroyed()) window.setTitle(appWindowTitle());
1258
1314
  });
1259
- window.on('focus', () => requestDesktopResumeAuthRecovery('window-focus'));
1315
+ window.on('focus', () => {
1316
+ requestDesktopResumeAuthRecovery('window-focus');
1317
+ void macosPermissionOnboardingOwner?.prime();
1318
+ });
1260
1319
  window.webContents.on('will-navigate', (event, url) => {
1261
1320
  if (isAllowedRuntimeUrl(url) || url === DESKTOP_RUNTIME_RECOVERY_PAGE_URL) return;
1262
1321
  event.preventDefault();
@@ -1550,8 +1609,8 @@ if (!app.requestSingleInstanceLock()) {
1550
1609
  log
1551
1610
  });
1552
1611
  desktopAutoStartOwner.reconcile('startup');
1553
- const macosPermissionOnboardingOwner = createMacosPermissionOnboardingOwner({
1554
- desktopCapturer,
1612
+ macosPermissionOnboardingOwner = createMacosPermissionOnboardingOwner({
1613
+ requestScreenRecording: requestMacosScreenRecordingAccess,
1555
1614
  systemPreferences,
1556
1615
  stateRoot,
1557
1616
  runtimeStateDir,
@@ -1559,10 +1618,11 @@ if (!app.requestSingleInstanceLock()) {
1559
1618
  bundleIdentifier: VUVODESK_MAC_BUNDLE_IDENTIFIER,
1560
1619
  log
1561
1620
  });
1562
- // Permission UI cannot hold Hub or Client startup. Marker claims are
1563
- // synchronous, so RemoteFast cannot race a second TCC request before the
1564
- // signed Electron Main request settles.
1565
- void macosPermissionOnboardingOwner.prime();
1621
+ // Claim native prompt ownership before RemoteFast starts, but wait until
1622
+ // the signed GUI app has a visible, focused window before asking macOS to
1623
+ // present consent UI. The asynchronous request never holds Hub/Client
1624
+ // startup or an automatic hidden launch.
1625
+ macosPermissionOnboardingOwner.claimNativePromptOwner();
1566
1626
  if (process.argv.includes('--updated')) {
1567
1627
  log(
1568
1628
  `desktop update event=post-install-startup platform=${process.platform} version=${app.getVersion()} marker=updated`,
@@ -1668,9 +1728,13 @@ if (!app.requestSingleInstanceLock()) {
1668
1728
  event.preventDefault();
1669
1729
  return;
1670
1730
  }
1671
- if (installerQuitDecision === InstallerQuitDecision.ALLOW) return;
1731
+ if (installerQuitDecision === InstallerQuitDecision.ALLOW) {
1732
+ stopMacosScreenPermissionRequest();
1733
+ return;
1734
+ }
1672
1735
  event.preventDefault();
1673
- recurringProductUpdates?.stop();
1736
+ recurringProductUpdates?.stop();
1737
+ stopMacosScreenPermissionRequest();
1674
1738
  quitSequenceStarted = true;
1675
1739
  isQuitting = true;
1676
1740
  void quiesceDesktopAuthOwner('application-quit').then(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.681",
3
+ "version": "0.1.683",
4
4
  "livedeskClientVersion": "0.1.274",
5
5
  "buildFlavor": "production",
6
6
  "description": "VuvoDesk Hub and client launcher",
package/web/dist/app.html CHANGED
@@ -52,7 +52,7 @@
52
52
  .vuvodesk-static-boot [hidden] { display: none; }
53
53
  @keyframes vuvodesk-static-boot-spin { to { transform: rotate(360deg); } }
54
54
  </style>
55
- <script type="module" crossorigin src="/assets/app-C0wgDWxx.js" data-vuvodesk-entry></script>
55
+ <script type="module" crossorigin src="/assets/app-CEj6iA0M.js" data-vuvodesk-entry></script>
56
56
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-DD1OZmLC.js">
57
57
  <link rel="modulepreload" crossorigin href="/assets/pwa-bootstrap-ljOQTt2e.js">
58
58
  </head>
@@ -4,7 +4,7 @@
4
4
  "short_name": "VuvoDesk",
5
5
  "description": "Monitor and control your VuvoDesk computers from your phone.",
6
6
  "lang": "en",
7
- "start_url": "/app?pwa=0.1.681",
7
+ "start_url": "/app?pwa=0.1.683",
8
8
  "scope": "/",
9
9
  "display": "standalone",
10
10
  "orientation": "any",
@@ -1 +1 @@
1
- import{j as t}from"./pwa-startup-DvbrYEQi.js";import{r as c,d as A,S as q,T as I,e as z}from"./icons-CuXJn-Hz.js";import{h as x}from"./MonitorStackIcon-Bnu-aE9X.js";import"./pwa-bootstrap-ljOQTt2e.js";const E={enabled:!0};function w(i){return{enabled:i?.enabled===!0}}async function L(i,p,f){let l=null;for(let s=0;s<2;s+=1){const g=await x("/api/settings",{signal:f},p);if(g.settings.agent?.enabled===i)return;try{await x("/api/settings",{method:"PATCH",signal:f,body:JSON.stringify({revision:g.revision,agent:{enabled:i}})},p);return}catch(a){if(l=a,s!==0||!String(a instanceof Error?a.message:a).startsWith("409 "))throw a}}throw l}function H({hubUrl:i,showToast:p}){const[f,l]=c.useState(E),[s,g]=c.useState(null),[a,m]=c.useState(""),[j,v]=c.useState(""),[y,d]=c.useState(""),b=c.useRef(0),u=c.useRef(null);c.useEffect(()=>{let e=!0;u.current?.abort();const n=new AbortController;u.current=n;const h=++b.current;return g(null),l(E),m(""),d(""),v(""),x("/api/settings/agent",{signal:n.signal},i).then(o=>{!e||n.signal.aborted||h!==b.current||(g(o.settings),l(w(o.settings)))}).catch(o=>{e&&!n.signal.aborted&&h===b.current&&d(o instanceof Error?o.message:String(o))}),()=>{e=!1,n.abort(),u.current===n&&(u.current=null)}},[i]);const k=(e,n)=>l(h=>({...h,[e]:n})),C=e=>{g(e),l(w(e))},S=()=>({generation:b.current,controller:u.current}),r=e=>e.generation===b.current&&e.controller!==null&&e.controller===u.current&&!e.controller.signal.aborted,R=async()=>{if(!s){d("Agent settings are not available yet.");return}const e=S();if(r(e)){m("save"),d(""),v("");try{if(await L(f.enabled,i,e.controller.signal),!r(e))return;const n=await x("/api/settings/agent",{signal:e.controller.signal},i);if(!r(e))return;C(n.settings),v("Codex Agent settings saved.")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},T=async()=>{const e=S();if(r(e)){m("test"),d(""),v("");try{const n=await x("/api/settings/agent/test",{method:"POST",signal:e.controller.signal,body:JSON.stringify({})},i);if(!r(e))return;const h=await x("/api/settings/agent",{signal:e.controller.signal},i);if(!r(e))return;C(h.settings);const o=Number.isFinite(n.connection?.latencyMs)?Math.max(0,Math.round(n.connection.latencyMs)):null;p(`Codex Agent connection verified${o!==null?` in ${o} ms.`:"."}`,"success")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},D=s?.codexInstallation==="installed"&&s.codexAuth==="signed-in",N=s!==null;return t.jsxs("div",{className:"settings-tab-content agent-settings-content",children:[t.jsxs("section",{className:"settings-section-card agent-enable-card",children:[t.jsxs("label",{className:"settings-toggle agent-enable-toggle",children:[t.jsx("input",{type:"checkbox",checked:f.enabled,onChange:e=>k("enabled",e.target.checked),disabled:!N||a!==""}),t.jsx("span",{children:"Enable Codex Agent"})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," Enabled by default. Agent commands use only the Codex account signed in on this Hub."]})]}),y&&t.jsx("div",{className:"agent-settings-alert error",role:"alert",children:y}),j&&t.jsx("div",{className:"agent-settings-alert success",role:"status",children:j}),t.jsxs("section",{className:"settings-section-card",children:[t.jsxs("div",{className:"settings-section-heading",children:[t.jsxs("div",{children:[t.jsx("span",{children:"Connection"}),t.jsx("h3",{children:"Codex SDK / CLI"})]}),t.jsx(q,{size:20})]}),t.jsxs("div",{className:"agent-connection-row",children:[t.jsx("span",{className:`agent-connection-dot ${D?"connected":s?.codexAuth==="not-signed-in"?"unavailable":""}`}),t.jsx("span",{children:s?.codexInstallation==="not-installed"?"Codex SDK not installed":s?.codexAuth==="signed-in"?"Codex CLI signed in":s?.codexAuth==="not-signed-in"?"Codex CLI sign-in required":"Codex status not tested"}),t.jsxs("button",{className:"settings-reset",onClick:()=>{T()},disabled:a!=="",type:"button",children:[t.jsx(I,{size:15})," ",a==="test"?"Testing":"Test connection"]})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," The Hub uses the installed Codex SDK / CLI to plan approved commands and run them through VuvoDesk Agent tools on selected computers. VuvoDesk never asks for separate credentials."]})]}),t.jsx("div",{className:"settings-action-row agent-settings-actions",children:t.jsxs("button",{className:"text-button primary",onClick:()=>{R()},disabled:!N||a!=="",type:"button",children:[t.jsx(z,{size:15})," ",a==="save"?"Saving":"Save Agent settings"]})})]})}export{H as AgentSettingsTab};
1
+ import{j as t}from"./pwa-startup-DvbrYEQi.js";import{r as c,d as A,S as q,T as I,e as z}from"./icons-CuXJn-Hz.js";import{h as x}from"./MonitorStackIcon-q_7f5mZs.js";import"./pwa-bootstrap-ljOQTt2e.js";const E={enabled:!0};function w(i){return{enabled:i?.enabled===!0}}async function L(i,p,f){let l=null;for(let s=0;s<2;s+=1){const g=await x("/api/settings",{signal:f},p);if(g.settings.agent?.enabled===i)return;try{await x("/api/settings",{method:"PATCH",signal:f,body:JSON.stringify({revision:g.revision,agent:{enabled:i}})},p);return}catch(a){if(l=a,s!==0||!String(a instanceof Error?a.message:a).startsWith("409 "))throw a}}throw l}function H({hubUrl:i,showToast:p}){const[f,l]=c.useState(E),[s,g]=c.useState(null),[a,m]=c.useState(""),[j,v]=c.useState(""),[y,d]=c.useState(""),b=c.useRef(0),u=c.useRef(null);c.useEffect(()=>{let e=!0;u.current?.abort();const n=new AbortController;u.current=n;const h=++b.current;return g(null),l(E),m(""),d(""),v(""),x("/api/settings/agent",{signal:n.signal},i).then(o=>{!e||n.signal.aborted||h!==b.current||(g(o.settings),l(w(o.settings)))}).catch(o=>{e&&!n.signal.aborted&&h===b.current&&d(o instanceof Error?o.message:String(o))}),()=>{e=!1,n.abort(),u.current===n&&(u.current=null)}},[i]);const k=(e,n)=>l(h=>({...h,[e]:n})),C=e=>{g(e),l(w(e))},S=()=>({generation:b.current,controller:u.current}),r=e=>e.generation===b.current&&e.controller!==null&&e.controller===u.current&&!e.controller.signal.aborted,R=async()=>{if(!s){d("Agent settings are not available yet.");return}const e=S();if(r(e)){m("save"),d(""),v("");try{if(await L(f.enabled,i,e.controller.signal),!r(e))return;const n=await x("/api/settings/agent",{signal:e.controller.signal},i);if(!r(e))return;C(n.settings),v("Codex Agent settings saved.")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},T=async()=>{const e=S();if(r(e)){m("test"),d(""),v("");try{const n=await x("/api/settings/agent/test",{method:"POST",signal:e.controller.signal,body:JSON.stringify({})},i);if(!r(e))return;const h=await x("/api/settings/agent",{signal:e.controller.signal},i);if(!r(e))return;C(h.settings);const o=Number.isFinite(n.connection?.latencyMs)?Math.max(0,Math.round(n.connection.latencyMs)):null;p(`Codex Agent connection verified${o!==null?` in ${o} ms.`:"."}`,"success")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},D=s?.codexInstallation==="installed"&&s.codexAuth==="signed-in",N=s!==null;return t.jsxs("div",{className:"settings-tab-content agent-settings-content",children:[t.jsxs("section",{className:"settings-section-card agent-enable-card",children:[t.jsxs("label",{className:"settings-toggle agent-enable-toggle",children:[t.jsx("input",{type:"checkbox",checked:f.enabled,onChange:e=>k("enabled",e.target.checked),disabled:!N||a!==""}),t.jsx("span",{children:"Enable Codex Agent"})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," Enabled by default. Agent commands use only the Codex account signed in on this Hub."]})]}),y&&t.jsx("div",{className:"agent-settings-alert error",role:"alert",children:y}),j&&t.jsx("div",{className:"agent-settings-alert success",role:"status",children:j}),t.jsxs("section",{className:"settings-section-card",children:[t.jsxs("div",{className:"settings-section-heading",children:[t.jsxs("div",{children:[t.jsx("span",{children:"Connection"}),t.jsx("h3",{children:"Codex SDK / CLI"})]}),t.jsx(q,{size:20})]}),t.jsxs("div",{className:"agent-connection-row",children:[t.jsx("span",{className:`agent-connection-dot ${D?"connected":s?.codexAuth==="not-signed-in"?"unavailable":""}`}),t.jsx("span",{children:s?.codexInstallation==="not-installed"?"Codex SDK not installed":s?.codexAuth==="signed-in"?"Codex CLI signed in":s?.codexAuth==="not-signed-in"?"Codex CLI sign-in required":"Codex status not tested"}),t.jsxs("button",{className:"settings-reset",onClick:()=>{T()},disabled:a!=="",type:"button",children:[t.jsx(I,{size:15})," ",a==="test"?"Testing":"Test connection"]})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," The Hub uses the installed Codex SDK / CLI to plan approved commands and run them through VuvoDesk Agent tools on selected computers. VuvoDesk never asks for separate credentials."]})]}),t.jsx("div",{className:"settings-action-row agent-settings-actions",children:t.jsxs("button",{className:"text-button primary",onClick:()=>{R()},disabled:!N||a!=="",type:"button",children:[t.jsx(z,{size:15})," ",a==="save"?"Saving":"Save Agent settings"]})})]})}export{H as AgentSettingsTab};
@@ -1,4 +1,4 @@
1
- import{j as t}from"./pwa-startup-DvbrYEQi.js";import{r as d,ae as ht,af as Ce,z as yt,G as ee,X as xe,ag as vt,ah as wt,ai as Ze,aj as kt,ak as bt,al as Se,b as H,am as Ct,p as xt,an as _e,o as St,ao as jt,S as Je,ap as At,a9 as Rt,c as Nt,aq as Pt,ar as It,A as Tt,as as ue,M as Xe}from"./icons-CuXJn-Hz.js";import{r as Et,h as F}from"./MonitorStackIcon-Bnu-aE9X.js";import"./pwa-bootstrap-ljOQTt2e.js";const Ot=d.forwardRef(function({value:s,onChange:n,onSubmit:r,onOpenSuggestedTasks:i,suggestedOpen:f=!1,suggestedTaskCount:c,disabled:v=!1,placeholder:S="Ask VuvoDesk to work across your machines...",compact:g=!1},x){return t.jsxs("form",{className:"agents-composer",onSubmit:w=>{w.preventDefault(),r()},children:[t.jsx("button",{className:`agents-suggested-trigger ${f?"active":""}`,type:"button","aria-label":c===void 0?"Open Suggested tasks":`Open Suggested tasks (${c} available)`,"aria-expanded":f,onClick:i,disabled:v,children:t.jsx(ht,{size:17})}),t.jsx("textarea",{ref:x,value:s,onChange:w=>n(w.target.value.slice(0,4e3)),placeholder:S,"aria-label":"Ask VuvoDesk",rows:g?1:2,disabled:v}),t.jsxs("button",{className:"agents-run-button",type:"submit",disabled:v||!s.trim(),children:[v?t.jsx(Ce,{className:"spin",size:16}):t.jsx(yt,{size:16}),v?"Running":"Run"]})]})}),$t=["read","processControl","serviceControl","applicationControl","fileRead","fileWrite","fileDelete","shell","script","softwareInstall","network","systemPower","systemConfiguration","userAccount"];function de(e){return e.deviceName?.trim()||e.hostname?.trim()||e.deviceId}function pe(e){return e.connected!==!0?!1:e.synthetic===!0?!0:!e.channels||e.channels.control===!0}function Dt(e){return e==="running"||e==="completed"||e==="failed"||e==="cancelled"?e:"queued"}function _t(e){switch(e){case"process.list":return"Process list";case"system.health":return"System health";case"gpu.status":return"GPU status";case"disk.status":return"Disk status";case"service.status":return"Service status";case"diagnostics.collect":return"Diagnostics";case"process.control":return"Process control";case"service.control":return"Service control";case"application.launch":return"Launch application";case"application.close":return"Close application";case"file.read":return"Read file";case"file.write":return"Write file";case"file.delete":return"Delete file";case"file.list":return"List directory";case"command.run":return"Run command";case"script.run":return"Run script";case"software.install":return"Install software";case"network.status":return"Network status";case"system.power":return"Power action";case"system.configure":return"System configuration";case"logs.collect":return"Collect logs"}}const Mt=64*1024,Me="[Earlier log output hidden by PWA display limit.]",Qe=new TextEncoder,zt=new TextDecoder("utf-8",{fatal:!0}),ze=16*1024,Lt=200,Le="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Ft="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Bt="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|credential|password|secret|cookie)",qt=/\b(?:sk-(?:proj-|ant-)?[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{8,}|(?:AKIA|ASIA)[A-Z0-9]{16}|AIza[A-Za-z0-9_-]{35}|npm_[A-Za-z0-9]{8,}|xox[a-z]-[A-Za-z0-9-]{8,}|(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,})\b/g;function Ut(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function Fe(e){return Qe.encode(e).byteLength}function Be(e,s){const n=Qe.encode(e);if(n.byteLength<=s)return{value:e,truncated:!1};let r=n.byteLength-s;for(;r<n.byteLength&&(n[r]&192)===128;)r+=1;let i=zt.decode(n.subarray(r));const f=i.indexOf(`
1
+ import{j as t}from"./pwa-startup-DvbrYEQi.js";import{r as d,ae as ht,af as Ce,z as yt,G as ee,X as xe,ag as vt,ah as wt,ai as Ze,aj as kt,ak as bt,al as Se,b as H,am as Ct,p as xt,an as _e,o as St,ao as jt,S as Je,ap as At,a9 as Rt,c as Nt,aq as Pt,ar as It,A as Tt,as as ue,M as Xe}from"./icons-CuXJn-Hz.js";import{r as Et,h as F}from"./MonitorStackIcon-q_7f5mZs.js";import"./pwa-bootstrap-ljOQTt2e.js";const Ot=d.forwardRef(function({value:s,onChange:n,onSubmit:r,onOpenSuggestedTasks:i,suggestedOpen:f=!1,suggestedTaskCount:c,disabled:v=!1,placeholder:S="Ask VuvoDesk to work across your machines...",compact:g=!1},x){return t.jsxs("form",{className:"agents-composer",onSubmit:w=>{w.preventDefault(),r()},children:[t.jsx("button",{className:`agents-suggested-trigger ${f?"active":""}`,type:"button","aria-label":c===void 0?"Open Suggested tasks":`Open Suggested tasks (${c} available)`,"aria-expanded":f,onClick:i,disabled:v,children:t.jsx(ht,{size:17})}),t.jsx("textarea",{ref:x,value:s,onChange:w=>n(w.target.value.slice(0,4e3)),placeholder:S,"aria-label":"Ask VuvoDesk",rows:g?1:2,disabled:v}),t.jsxs("button",{className:"agents-run-button",type:"submit",disabled:v||!s.trim(),children:[v?t.jsx(Ce,{className:"spin",size:16}):t.jsx(yt,{size:16}),v?"Running":"Run"]})]})}),$t=["read","processControl","serviceControl","applicationControl","fileRead","fileWrite","fileDelete","shell","script","softwareInstall","network","systemPower","systemConfiguration","userAccount"];function de(e){return e.deviceName?.trim()||e.hostname?.trim()||e.deviceId}function pe(e){return e.connected!==!0?!1:e.synthetic===!0?!0:!e.channels||e.channels.control===!0}function Dt(e){return e==="running"||e==="completed"||e==="failed"||e==="cancelled"?e:"queued"}function _t(e){switch(e){case"process.list":return"Process list";case"system.health":return"System health";case"gpu.status":return"GPU status";case"disk.status":return"Disk status";case"service.status":return"Service status";case"diagnostics.collect":return"Diagnostics";case"process.control":return"Process control";case"service.control":return"Service control";case"application.launch":return"Launch application";case"application.close":return"Close application";case"file.read":return"Read file";case"file.write":return"Write file";case"file.delete":return"Delete file";case"file.list":return"List directory";case"command.run":return"Run command";case"script.run":return"Run script";case"software.install":return"Install software";case"network.status":return"Network status";case"system.power":return"Power action";case"system.configure":return"System configuration";case"logs.collect":return"Collect logs"}}const Mt=64*1024,Me="[Earlier log output hidden by PWA display limit.]",Qe=new TextEncoder,zt=new TextDecoder("utf-8",{fatal:!0}),ze=16*1024,Lt=200,Le="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Ft="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Bt="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|credential|password|secret|cookie)",qt=/\b(?:sk-(?:proj-|ant-)?[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{8,}|(?:AKIA|ASIA)[A-Z0-9]{16}|AIza[A-Za-z0-9_-]{35}|npm_[A-Za-z0-9]{8,}|xox[a-z]-[A-Za-z0-9-]{8,}|(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,})\b/g;function Ut(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function Fe(e){return Qe.encode(e).byteLength}function Be(e,s){const n=Qe.encode(e);if(n.byteLength<=s)return{value:e,truncated:!1};let r=n.byteLength-s;for(;r<n.byteLength&&(n[r]&192)===128;)r+=1;let i=zt.decode(n.subarray(r));const f=i.indexOf(`
2
2
  `);return f>=0&&f<i.length-1&&(i=i.slice(f+1)),{value:i,truncated:!0}}function et(e){return e.replace(/-----BEGIN [^-\r\n]*PRIVATE KEY-----[\s\S]*?-----END [^-\r\n]*PRIVATE KEY-----/gi,"[REDACTED PRIVATE KEY]").replace(/\b(?:proxy-)?authorization\s*[:=]\s*[^\r\n]*|\b(?:set-)?cookie\s*[:=]\s*[^\r\n]*/gi,"credential-header=[REDACTED]").replace(/\bBearer\s+(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi,"Bearer [REDACTED]").replace(new RegExp(`("${Le}"\\s*:\\s*")[^"\\r\\n]*(")`,"gi"),"$1[REDACTED]$2").replace(new RegExp(`('${Le}'\\s*:\\s*')[^'\\r\\n]*(')`,"gi"),"$1[REDACTED]$2").replace(new RegExp(`((?:[A-Za-z0-9.-]+[_-])?${Ft}\\s*[:=]\\s*)(?!\\[REDACTED\\])[^\\s,;&]+`,"gi"),"$1[REDACTED]").replace(new RegExp(`([?&]${Bt}=)[^&\\s]+`,"gi"),"$1[REDACTED]").replace(/\b(?:eyJ|[A-Za-z0-9_-]{8,}\.)[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,"[REDACTED TOKEN]").replace(qt,"[REDACTED KEY]")}function Gt(e){return typeof e!="string"?"source unknown":et(e).replace(/[\u0000-\u001f\u007f]/g," ").trim().slice(0,64)||"source unknown"}function qe(e){return typeof e=="number"&&Number.isSafeInteger(e)&&e>=0?Math.floor(e):null}function Vt(e){if(typeof e!="string"||e.length===0)return{output:"",displayTruncated:!1};const s=Be(e.replace(/\r\n?/g,`
3
3
  `),Mt);let n=et(s.value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g," ").trim(),r=s.truncated;const i=n?n.split(`
4
4
  `):[];if(i.length>Lt-1&&(n=i.slice(-199).join(`
@@ -1 +1 @@
1
- import{j as e}from"./pwa-startup-DvbrYEQi.js";import{r as i,R as x,E as j,D as u,f as g,h as f}from"./icons-CuXJn-Hz.js";import{l as b,c as d,d as y}from"./HubApp-CVaXT8PJ.js";import"./pwa-bootstrap-ljOQTt2e.js";import"./preload-helper-DD1OZmLC.js";import"./MonitorStackIcon-Bnu-aE9X.js";import"./supabase-C7qjtLN3.js";import"./frame-lifecycle-D91nchvm.js";function z({hubUrl:s,onToast:n}){const[o,m]=i.useState([]),[h,c]=i.useState(!0),r=i.useCallback(async()=>{c(!0);try{m((await b(s)).captures||[])}catch(t){n(t instanceof Error?t.message:String(t))}finally{c(!1)}},[s,n]);i.useEffect(()=>{r()},[r]);const p=async t=>{const a=d(t.id,s);if(navigator.share)try{await navigator.share({title:t.fileName,url:a});return}catch{}window.open(a,"_blank","noopener,noreferrer")};return e.jsxs("section",{className:"capture-gallery",children:[e.jsxs("header",{children:[e.jsxs("div",{children:[e.jsx("span",{className:"settings-eyebrow",children:"Capture Gallery"}),e.jsx("h2",{children:"Saved captures"})]}),e.jsxs("button",{className:"text-button",onClick:()=>{r()},type:"button",children:[e.jsx(x,{size:15})," Refresh"]})]}),h?e.jsx("p",{className:"settings-help",children:"Loading captures..."}):o.length===0?e.jsx("p",{className:"settings-help",children:"No captures saved yet."}):e.jsx("div",{className:"capture-gallery-grid",children:o.map(t=>{const a=d(t.id,s);return e.jsxs("article",{className:"capture-gallery-card",children:[t.mimeType.startsWith("image/")?e.jsx("img",{src:a,alt:t.target.label}):e.jsx("video",{src:a,controls:!0,preload:"metadata"}),e.jsxs("div",{className:"capture-gallery-card-body",children:[e.jsx("strong",{children:t.fileName}),e.jsxs("small",{children:[t.target.label," · ",new Date(t.createdAt).toLocaleString()," · ",Math.ceil(t.sizeBytes/1024)," KB"]}),e.jsxs("div",{children:[e.jsx("button",{className:"icon-button",title:"Open","aria-label":"Open",onClick:()=>window.open(a,"_blank","noopener,noreferrer"),type:"button",children:e.jsx(j,{size:15})}),e.jsx("a",{className:"icon-button",title:"Download","aria-label":"Download",href:`${a}?download=1`,download:t.fileName,children:e.jsx(u,{size:15})}),e.jsx("button",{className:"icon-button",title:"Share","aria-label":"Share",onClick:()=>{p(t)},type:"button",children:e.jsx(g,{size:15})}),e.jsx("button",{className:"icon-button danger",title:"Delete","aria-label":"Delete",onClick:()=>{window.confirm(`Delete ${t.fileName}?`)&&y(t.id,s).then(()=>r()).catch(l=>n(l instanceof Error?l.message:String(l)))},type:"button",children:e.jsx(f,{size:15})})]})]})]},t.id)})})]})}export{z as CaptureGallery};
1
+ import{j as e}from"./pwa-startup-DvbrYEQi.js";import{r as i,R as x,E as j,D as u,f as g,h as f}from"./icons-CuXJn-Hz.js";import{l as b,c as d,d as y}from"./HubApp-BOX09IGB.js";import"./pwa-bootstrap-ljOQTt2e.js";import"./preload-helper-DD1OZmLC.js";import"./MonitorStackIcon-q_7f5mZs.js";import"./supabase-C7qjtLN3.js";import"./frame-lifecycle-D91nchvm.js";function z({hubUrl:s,onToast:n}){const[o,m]=i.useState([]),[h,c]=i.useState(!0),r=i.useCallback(async()=>{c(!0);try{m((await b(s)).captures||[])}catch(t){n(t instanceof Error?t.message:String(t))}finally{c(!1)}},[s,n]);i.useEffect(()=>{r()},[r]);const p=async t=>{const a=d(t.id,s);if(navigator.share)try{await navigator.share({title:t.fileName,url:a});return}catch{}window.open(a,"_blank","noopener,noreferrer")};return e.jsxs("section",{className:"capture-gallery",children:[e.jsxs("header",{children:[e.jsxs("div",{children:[e.jsx("span",{className:"settings-eyebrow",children:"Capture Gallery"}),e.jsx("h2",{children:"Saved captures"})]}),e.jsxs("button",{className:"text-button",onClick:()=>{r()},type:"button",children:[e.jsx(x,{size:15})," Refresh"]})]}),h?e.jsx("p",{className:"settings-help",children:"Loading captures..."}):o.length===0?e.jsx("p",{className:"settings-help",children:"No captures saved yet."}):e.jsx("div",{className:"capture-gallery-grid",children:o.map(t=>{const a=d(t.id,s);return e.jsxs("article",{className:"capture-gallery-card",children:[t.mimeType.startsWith("image/")?e.jsx("img",{src:a,alt:t.target.label}):e.jsx("video",{src:a,controls:!0,preload:"metadata"}),e.jsxs("div",{className:"capture-gallery-card-body",children:[e.jsx("strong",{children:t.fileName}),e.jsxs("small",{children:[t.target.label," · ",new Date(t.createdAt).toLocaleString()," · ",Math.ceil(t.sizeBytes/1024)," KB"]}),e.jsxs("div",{children:[e.jsx("button",{className:"icon-button",title:"Open","aria-label":"Open",onClick:()=>window.open(a,"_blank","noopener,noreferrer"),type:"button",children:e.jsx(j,{size:15})}),e.jsx("a",{className:"icon-button",title:"Download","aria-label":"Download",href:`${a}?download=1`,download:t.fileName,children:e.jsx(u,{size:15})}),e.jsx("button",{className:"icon-button",title:"Share","aria-label":"Share",onClick:()=>{p(t)},type:"button",children:e.jsx(g,{size:15})}),e.jsx("button",{className:"icon-button danger",title:"Delete","aria-label":"Delete",onClick:()=>{window.confirm(`Delete ${t.fileName}?`)&&y(t.id,s).then(()=>r()).catch(l=>n(l instanceof Error?l.message:String(l)))},type:"button",children:e.jsx(f,{size:15})})]})]})]},t.id)})})]})}export{z as CaptureGallery};