ft-scout 8.0.3 → 8.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.firebase/hosting.d2Vi.cache +6 -6
- package/README.md +29 -13
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +48 -18
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/audit.d.ts.map +1 -1
- package/bin/src/commands/audit.js +8 -2
- package/bin/src/commands/audit.js.map +1 -1
- package/bin/src/commands/dashboard.d.ts.map +1 -1
- package/bin/src/commands/dashboard.js +9 -7
- package/bin/src/commands/dashboard.js.map +1 -1
- package/bin/src/commands/plan.d.ts.map +1 -0
- package/bin/src/commands/plan.js +70 -0
- package/bin/src/commands/plan.js.map +1 -0
- package/bin/src/commands/quota.d.ts.map +1 -1
- package/bin/src/commands/quota.js +4 -20
- package/bin/src/commands/quota.js.map +1 -1
- package/bin/src/commands/risky.d.ts.map +1 -1
- package/bin/src/commands/risky.js +8 -2
- package/bin/src/commands/risky.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +16 -4
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/appControl.d.ts.map +1 -1
- package/bin/src/engine/appControl.js +287 -17
- package/bin/src/engine/appControl.js.map +1 -1
- package/bin/src/engine/liveScreenEngine.d.ts.map +1 -0
- package/bin/src/engine/liveScreenEngine.js +390 -0
- package/bin/src/engine/liveScreenEngine.js.map +1 -0
- package/bin/src/engine/voiceEngine.d.ts.map +1 -1
- package/bin/src/engine/voiceEngine.js +1 -0
- package/bin/src/engine/voiceEngine.js.map +1 -1
- package/bin/src/index.js +18 -7
- package/bin/src/index.js.map +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +57 -0
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/auth.d.ts.map +1 -1
- package/bin/src/utils/auth.js +382 -7
- package/bin/src/utils/auth.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/payment.d.ts.map +1 -0
- package/bin/src/utils/payment.js +251 -0
- package/bin/src/utils/payment.js.map +1 -0
- package/firebase.json +20 -6
- package/package.json +1 -1
- package/web/app.js +97 -16
- package/web/styles.css +96 -1
|
@@ -2,11 +2,12 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import http from 'http';
|
|
4
4
|
import https from 'https';
|
|
5
|
-
import { exec } from 'child_process';
|
|
5
|
+
import { exec, spawn, ChildProcess } from 'child_process';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import OpenAI from 'openai';
|
|
8
8
|
import dotenv from 'dotenv';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
+
import { startLiveScreenShare, stopLiveScreenShare, isLiveScreenShareActive, getLiveScreenFrame, analyzeLiveScreen, callVisionAiWithFrame, setLiveScreenTargetApp, getLiveScreenTargetApp, } from './liveScreenEngine.js';
|
|
10
11
|
dotenv.config({ quiet: true });
|
|
11
12
|
const execAsync = promisify(exec);
|
|
12
13
|
/**
|
|
@@ -183,6 +184,9 @@ export async function findInstalledApp(query) {
|
|
|
183
184
|
*/
|
|
184
185
|
export async function openApp(appNameOrPath, target, options) {
|
|
185
186
|
const { category, name } = normalizeAppName(appNameOrPath);
|
|
187
|
+
if (name && name !== 'desktop' && name !== 'system') {
|
|
188
|
+
setLiveScreenTargetApp(name);
|
|
189
|
+
}
|
|
186
190
|
const platform = process.platform;
|
|
187
191
|
let command = '';
|
|
188
192
|
const cleanTarget = target ? target.trim() : '';
|
|
@@ -352,6 +356,10 @@ export async function openApp(appNameOrPath, target, options) {
|
|
|
352
356
|
export async function executeInApp(appName, action, payload) {
|
|
353
357
|
const { category, name } = normalizeAppName(appName);
|
|
354
358
|
const actLower = (action || '').trim().toLowerCase();
|
|
359
|
+
// Focus live screen sharing on the active app being operated
|
|
360
|
+
if (name && name !== 'desktop' && name !== 'system') {
|
|
361
|
+
setLiveScreenTargetApp(name);
|
|
362
|
+
}
|
|
355
363
|
// Universal Scratchpad Desktop Automation Actions (Takeover, Screen Seeing, Keystrokes, Mouse Clicks, Focus Locks)
|
|
356
364
|
if (['takeover', 'takeover_control', 'takeover_screen', 'takeover_app'].includes(actLower)) {
|
|
357
365
|
const parsed = parsePayload(payload);
|
|
@@ -376,7 +384,38 @@ export async function executeInApp(appName, action, payload) {
|
|
|
376
384
|
output: res.output,
|
|
377
385
|
};
|
|
378
386
|
}
|
|
387
|
+
if (['start_screen_share', 'live_screen_share', 'screen_share', 'start_live_stream'].includes(actLower)) {
|
|
388
|
+
const targetApp = (name && name !== 'desktop' && name !== 'system') ? name : undefined;
|
|
389
|
+
const res = await startLiveScreenShare({ targetApp });
|
|
390
|
+
return {
|
|
391
|
+
success: res.success,
|
|
392
|
+
app: name || appName,
|
|
393
|
+
action: actLower,
|
|
394
|
+
output: res.message,
|
|
395
|
+
details: { active: true, targetApp },
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (['stop_screen_share', 'end_screen_share', 'stop_live_stream'].includes(actLower)) {
|
|
399
|
+
const res = stopLiveScreenShare();
|
|
400
|
+
return {
|
|
401
|
+
success: res.success,
|
|
402
|
+
app: name || appName,
|
|
403
|
+
action: actLower,
|
|
404
|
+
output: res.summary,
|
|
405
|
+
details: { active: false },
|
|
406
|
+
};
|
|
407
|
+
}
|
|
379
408
|
if (['capture_screen', 'screenshot', 'screen_shot'].includes(actLower)) {
|
|
409
|
+
if (isLiveScreenShareActive()) {
|
|
410
|
+
const liveRes = await analyzeLiveScreen();
|
|
411
|
+
return {
|
|
412
|
+
success: liveRes.success,
|
|
413
|
+
app: name || appName,
|
|
414
|
+
action: actLower,
|
|
415
|
+
output: liveRes.output,
|
|
416
|
+
details: liveRes.details,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
380
419
|
const res = await captureScreen(name || appName);
|
|
381
420
|
return {
|
|
382
421
|
success: res.success,
|
|
@@ -386,9 +425,9 @@ export async function executeInApp(appName, action, payload) {
|
|
|
386
425
|
details: res.details,
|
|
387
426
|
};
|
|
388
427
|
}
|
|
389
|
-
if (['see_screen', 'inspect_screen', 'view_screen', 'analyze_screen', 'inspect_desktop', 'visual_analysis', 'see_and_analyze'].includes(actLower)) {
|
|
428
|
+
if (['see_screen', 'inspect_screen', 'view_screen', 'analyze_screen', 'inspect_desktop', 'visual_analysis', 'see_and_analyze', 'live_screen_stream', 'screen_stream'].includes(actLower)) {
|
|
390
429
|
const query = typeof payload === 'string' ? payload : (payload?.query || payload?.target || payload?.text || undefined);
|
|
391
|
-
const res = await
|
|
430
|
+
const res = await analyzeLiveScreen(query);
|
|
392
431
|
return {
|
|
393
432
|
success: res.success,
|
|
394
433
|
app: name || appName,
|
|
@@ -1030,12 +1069,208 @@ export async function getScreenInfo(appName) {
|
|
|
1030
1069
|
};
|
|
1031
1070
|
}
|
|
1032
1071
|
}
|
|
1072
|
+
let persistentAuraProcess = null;
|
|
1073
|
+
let persistentAuraFlagPath = null;
|
|
1074
|
+
/**
|
|
1075
|
+
* Start persistent futuristic sky-blue aura overlay on screen borders and corners.
|
|
1076
|
+
* Stays continuously active and animating as long as live screen sharing is running.
|
|
1077
|
+
*/
|
|
1078
|
+
export async function startScreenAuraOverlay(message = "Live Screen Share Active • Streaming to Model") {
|
|
1079
|
+
const platform = process.platform;
|
|
1080
|
+
if (platform !== 'win32')
|
|
1081
|
+
return;
|
|
1082
|
+
// Stop any existing overlay first
|
|
1083
|
+
stopScreenAuraOverlay();
|
|
1084
|
+
try {
|
|
1085
|
+
const scriptsDir = path.join(process.cwd(), '.ft', 'scripts');
|
|
1086
|
+
if (!fs.existsSync(scriptsDir)) {
|
|
1087
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
1088
|
+
}
|
|
1089
|
+
const psFile = path.join(scriptsDir, 'scout_aura_persistent.ps1');
|
|
1090
|
+
const flagFile = path.join(scriptsDir, 'aura_active.flag');
|
|
1091
|
+
fs.writeFileSync(flagFile, String(Date.now()), 'utf8');
|
|
1092
|
+
persistentAuraFlagPath = flagFile;
|
|
1093
|
+
const subtitleText = " Perception Active";
|
|
1094
|
+
const parentPid = process.pid;
|
|
1095
|
+
const overlayPs = `
|
|
1096
|
+
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
|
|
1097
|
+
|
|
1098
|
+
[xml]$xaml = @"
|
|
1099
|
+
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
1100
|
+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
1101
|
+
Title="Scout Aura Overlay"
|
|
1102
|
+
WindowStyle="None"
|
|
1103
|
+
AllowsTransparency="True"
|
|
1104
|
+
Background="Transparent"
|
|
1105
|
+
Topmost="True"
|
|
1106
|
+
ShowInTaskbar="False"
|
|
1107
|
+
ShowActivated="False"
|
|
1108
|
+
IsHitTestVisible="False">
|
|
1109
|
+
<Window.Resources>
|
|
1110
|
+
<Storyboard x:Key="AuraPulse" RepeatBehavior="Forever" AutoReverse="True">
|
|
1111
|
+
<DoubleAnimation Storyboard.TargetName="AuraBorder" Storyboard.TargetProperty="Opacity"
|
|
1112
|
+
From="0.4" To="1.0" Duration="0:0:0.6"/>
|
|
1113
|
+
<DoubleAnimation Storyboard.TargetName="CornerTL" Storyboard.TargetProperty="Opacity"
|
|
1114
|
+
From="0.5" To="1.0" Duration="0:0:0.6"/>
|
|
1115
|
+
<DoubleAnimation Storyboard.TargetName="CornerTR" Storyboard.TargetProperty="Opacity"
|
|
1116
|
+
From="0.5" To="1.0" Duration="0:0:0.6"/>
|
|
1117
|
+
<DoubleAnimation Storyboard.TargetName="CornerBL" Storyboard.TargetProperty="Opacity"
|
|
1118
|
+
From="0.5" To="1.0" Duration="0:0:0.6"/>
|
|
1119
|
+
<DoubleAnimation Storyboard.TargetName="CornerBR" Storyboard.TargetProperty="Opacity"
|
|
1120
|
+
From="0.5" To="1.0" Duration="0:0:0.6"/>
|
|
1121
|
+
<DoubleAnimation Storyboard.TargetName="AuraGlow" Storyboard.TargetProperty="Opacity"
|
|
1122
|
+
From="0.25" To="0.9" Duration="0:0:0.6"/>
|
|
1123
|
+
</Storyboard>
|
|
1124
|
+
</Window.Resources>
|
|
1125
|
+
|
|
1126
|
+
<Grid IsHitTestVisible="False">
|
|
1127
|
+
<!-- Outer Glowing Sky Blue Aura Border -->
|
|
1128
|
+
<Border x:Name="AuraGlow" BorderThickness="6" BorderBrush="#00d8ff" Margin="0">
|
|
1129
|
+
<Border.Effect>
|
|
1130
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="45" ShadowDepth="0" Opacity="0.95"/>
|
|
1131
|
+
</Border.Effect>
|
|
1132
|
+
</Border>
|
|
1133
|
+
<Border x:Name="AuraBorder" BorderThickness="3" BorderBrush="#38bdf8" Margin="0">
|
|
1134
|
+
<Border.Effect>
|
|
1135
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1136
|
+
</Border.Effect>
|
|
1137
|
+
</Border>
|
|
1138
|
+
|
|
1139
|
+
<!-- Sky Blue Corner Brackets with Aura Glow -->
|
|
1140
|
+
<!-- Top Left Corner -->
|
|
1141
|
+
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" Height="120" Margin="6,6,0,0">
|
|
1142
|
+
<Path x:Name="CornerTL" Data="M 0,90 L 0,0 L 90,0" Stroke="#00e5ff" StrokeThickness="6">
|
|
1143
|
+
<Path.Effect>
|
|
1144
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="30" ShadowDepth="0" Opacity="1"/>
|
|
1145
|
+
</Path.Effect>
|
|
1146
|
+
</Path>
|
|
1147
|
+
</Canvas>
|
|
1148
|
+
|
|
1149
|
+
<!-- Top Right Corner -->
|
|
1150
|
+
<Canvas HorizontalAlignment="Right" VerticalAlignment="Top" Width="120" Height="120" Margin="0,6,6,0">
|
|
1151
|
+
<Path x:Name="CornerTR" Data="M 30,0 L 120,0 L 120,90" Stroke="#00e5ff" StrokeThickness="6">
|
|
1152
|
+
<Path.Effect>
|
|
1153
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="30" ShadowDepth="0" Opacity="1"/>
|
|
1154
|
+
</Path.Effect>
|
|
1155
|
+
</Path>
|
|
1156
|
+
</Canvas>
|
|
1157
|
+
|
|
1158
|
+
<!-- Bottom Left Corner -->
|
|
1159
|
+
<Canvas HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="120" Height="120" Margin="6,0,0,6">
|
|
1160
|
+
<Path x:Name="CornerBL" Data="M 0,30 L 0,120 L 90,120" Stroke="#00e5ff" StrokeThickness="6">
|
|
1161
|
+
<Path.Effect>
|
|
1162
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="30" ShadowDepth="0" Opacity="1"/>
|
|
1163
|
+
</Path.Effect>
|
|
1164
|
+
</Path>
|
|
1165
|
+
</Canvas>
|
|
1166
|
+
|
|
1167
|
+
<!-- Bottom Right Corner -->
|
|
1168
|
+
<Canvas HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="120" Height="120" Margin="0,0,6,6">
|
|
1169
|
+
<Path x:Name="CornerBR" Data="M 30,120 L 120,120 L 120,30" Stroke="#00e5ff" StrokeThickness="6">
|
|
1170
|
+
<Path.Effect>
|
|
1171
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="30" ShadowDepth="0" Opacity="1"/>
|
|
1172
|
+
</Path.Effect>
|
|
1173
|
+
</Path>
|
|
1174
|
+
</Canvas>
|
|
1175
|
+
|
|
1176
|
+
<!-- Top Floating Status Badge -->
|
|
1177
|
+
<Border HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,22,0,0"
|
|
1178
|
+
Background="#EE03121F" BorderBrush="#00e5ff" BorderThickness="1.8" CornerRadius="24" Padding="26,10">
|
|
1179
|
+
<Border.Effect>
|
|
1180
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="0.95"/>
|
|
1181
|
+
</Border.Effect>
|
|
1182
|
+
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
|
1183
|
+
<Ellipse Width="12" Height="12" Fill="#00e5ff" Margin="0,0,12,0">
|
|
1184
|
+
<Ellipse.Effect>
|
|
1185
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="12" ShadowDepth="0"/>
|
|
1186
|
+
</Ellipse.Effect>
|
|
1187
|
+
</Ellipse>
|
|
1188
|
+
<TextBlock Text="${message}" Foreground="#ffffff" FontSize="16" FontWeight="Bold" VerticalAlignment="Center"/>
|
|
1189
|
+
<TextBlock Text="${subtitleText}" Foreground="#38bdf8" FontSize="13.5" FontWeight="SemiBold" Margin="10,0,0,0" VerticalAlignment="Center"/>
|
|
1190
|
+
</StackPanel>
|
|
1191
|
+
</Border>
|
|
1192
|
+
</Grid>
|
|
1193
|
+
</Window>
|
|
1194
|
+
"@
|
|
1195
|
+
|
|
1196
|
+
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
|
|
1197
|
+
$window = [System.Windows.Markup.XamlReader]::Load($reader)
|
|
1198
|
+
|
|
1199
|
+
$window.Left = [System.Windows.SystemParameters]::VirtualScreenLeft
|
|
1200
|
+
$window.Top = [System.Windows.SystemParameters]::VirtualScreenTop
|
|
1201
|
+
$window.Width = [System.Windows.SystemParameters]::VirtualScreenWidth
|
|
1202
|
+
$window.Height = [System.Windows.SystemParameters]::VirtualScreenHeight
|
|
1203
|
+
$window.Topmost = $true
|
|
1204
|
+
$window.ShowActivated = $false
|
|
1205
|
+
|
|
1206
|
+
$storyboard = $window.Resources["AuraPulse"]
|
|
1207
|
+
$window.Add_Loaded({
|
|
1208
|
+
$storyboard.Begin($window)
|
|
1209
|
+
})
|
|
1210
|
+
|
|
1211
|
+
$flagPath = "${flagFile.replace(/\\/g, '\\\\')}"
|
|
1212
|
+
$parentPid = ${parentPid}
|
|
1213
|
+
|
|
1214
|
+
# Check if screen sharing is active or parent exited every 350ms
|
|
1215
|
+
$timer = New-Object System.Windows.Threading.DispatcherTimer
|
|
1216
|
+
$timer.Interval = [TimeSpan]::FromMilliseconds(350)
|
|
1217
|
+
$timer.Add_Tick({
|
|
1218
|
+
$shouldClose = $false
|
|
1219
|
+
if (-not (Test-Path $flagPath)) {
|
|
1220
|
+
$shouldClose = $true
|
|
1221
|
+
} else {
|
|
1222
|
+
$proc = Get-Process -Id $parentPid -ErrorAction SilentlyContinue
|
|
1223
|
+
if (-not $proc) {
|
|
1224
|
+
$shouldClose = $true
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if ($shouldClose) {
|
|
1228
|
+
$timer.Stop()
|
|
1229
|
+
$window.Close()
|
|
1230
|
+
}
|
|
1231
|
+
})
|
|
1232
|
+
$timer.Start()
|
|
1233
|
+
|
|
1234
|
+
[void]$window.ShowDialog()
|
|
1235
|
+
`;
|
|
1236
|
+
fs.writeFileSync(psFile, overlayPs, 'utf8');
|
|
1237
|
+
const child = spawn('powershell', ['-WindowStyle', 'Hidden', '-NoProfile', '-Sta', '-ExecutionPolicy', 'Bypass', '-File', psFile], {
|
|
1238
|
+
detached: false,
|
|
1239
|
+
stdio: 'ignore',
|
|
1240
|
+
});
|
|
1241
|
+
child.unref();
|
|
1242
|
+
persistentAuraProcess = child;
|
|
1243
|
+
}
|
|
1244
|
+
catch { }
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Stop persistent screen aura animation when live screen sharing stops.
|
|
1248
|
+
*/
|
|
1249
|
+
export function stopScreenAuraOverlay() {
|
|
1250
|
+
if (persistentAuraFlagPath && fs.existsSync(persistentAuraFlagPath)) {
|
|
1251
|
+
try {
|
|
1252
|
+
fs.unlinkSync(persistentAuraFlagPath);
|
|
1253
|
+
}
|
|
1254
|
+
catch { }
|
|
1255
|
+
persistentAuraFlagPath = null;
|
|
1256
|
+
}
|
|
1257
|
+
if (persistentAuraProcess) {
|
|
1258
|
+
try {
|
|
1259
|
+
persistentAuraProcess.kill();
|
|
1260
|
+
}
|
|
1261
|
+
catch { }
|
|
1262
|
+
persistentAuraProcess = null;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1033
1265
|
/**
|
|
1034
1266
|
* Display a futuristic sky-blue aura animation on screen borders and corners,
|
|
1035
|
-
* show the warning "Scout is on the screen.", and block external inputs
|
|
1036
|
-
*
|
|
1267
|
+
* show the warning "Scout is on the screen.", and optionally block external inputs.
|
|
1268
|
+
* If durationMs <= 0, aura stays on continuously until stopScreenAuraOverlay() is called.
|
|
1037
1269
|
*/
|
|
1038
1270
|
export async function showScreenAuraOverlay(durationMs = 2200, message = "Scout is viewing the screen.", blockInput = false) {
|
|
1271
|
+
if (durationMs <= 0) {
|
|
1272
|
+
return await startScreenAuraOverlay(message);
|
|
1273
|
+
}
|
|
1039
1274
|
const platform = process.platform;
|
|
1040
1275
|
if (platform !== 'win32')
|
|
1041
1276
|
return;
|
|
@@ -1053,7 +1288,7 @@ try { [void]$b::BlockInput($true) } catch {}
|
|
|
1053
1288
|
const unblockSnippet = blockInput ? `
|
|
1054
1289
|
try { [void]$b::BlockInput($false) } catch {}
|
|
1055
1290
|
` : '';
|
|
1056
|
-
const subtitleText = blockInput ? " • External Input Locked" : "
|
|
1291
|
+
const subtitleText = blockInput ? " • External Input Locked" : " Perception Active";
|
|
1057
1292
|
const overlayPs = `
|
|
1058
1293
|
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
|
|
1059
1294
|
${blockInputSnippet}
|
|
@@ -1457,12 +1692,18 @@ export async function analyzeScreen(appNameOrScreen, targetQuery) {
|
|
|
1457
1692
|
*/
|
|
1458
1693
|
export async function takeoverControl(appName, durationMs = 3000, x, y) {
|
|
1459
1694
|
const { category, name } = normalizeAppName(appName);
|
|
1695
|
+
const targetApp = (name && name !== 'desktop' && name !== 'system') ? name : undefined;
|
|
1696
|
+
if (targetApp) {
|
|
1697
|
+
setLiveScreenTargetApp(targetApp);
|
|
1698
|
+
}
|
|
1460
1699
|
if (name && name !== 'desktop' && name !== 'system' && category !== 'browser') {
|
|
1461
1700
|
await openApp(name);
|
|
1462
1701
|
}
|
|
1463
1702
|
// Trigger visual sky-blue aura overlay on screen borders & corners with warning badge & input block
|
|
1464
1703
|
try {
|
|
1465
|
-
|
|
1704
|
+
const auraLabel = targetApp ? `Scout is controlling ${targetApp}...` : "Scout is on the screen.";
|
|
1705
|
+
await showScreenAuraOverlay(Math.min(5000, Math.max(800, durationMs)), auraLabel, true);
|
|
1706
|
+
startLiveScreenShare({ targetApp }).catch(() => { });
|
|
1466
1707
|
}
|
|
1467
1708
|
catch { }
|
|
1468
1709
|
const mouseRes = await moveAndClickMouse(x, y, 'left', name || appName);
|
|
@@ -1946,23 +2187,22 @@ $m::mouse_event(0x0800, 0, 0, ${delta}, 0)
|
|
|
1946
2187
|
* Visually ground and click a target UI element by name or visual description.
|
|
1947
2188
|
*/
|
|
1948
2189
|
export async function findAndClickElement(elementQuery, appName, button = 'left') {
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
return { success: false, output: `Could not capture screen to locate element: ${capture.output}` };
|
|
1952
|
-
}
|
|
1953
|
-
const screenWidth = capture.details.width || 1920;
|
|
1954
|
-
const screenHeight = capture.details.height || 1080;
|
|
1955
|
-
const outPath = capture.details.outPath;
|
|
2190
|
+
let screenWidth = 1920;
|
|
2191
|
+
let screenHeight = 1080;
|
|
1956
2192
|
let targetX = undefined;
|
|
1957
2193
|
let targetY = undefined;
|
|
1958
|
-
|
|
2194
|
+
// Use in-memory live screen frame first without writing screenshot files to disk
|
|
2195
|
+
const liveFrame = await getLiveScreenFrame();
|
|
2196
|
+
if (liveFrame) {
|
|
2197
|
+
screenWidth = liveFrame.width;
|
|
2198
|
+
screenHeight = liveFrame.height;
|
|
1959
2199
|
try {
|
|
1960
|
-
const prompt = `Look at this
|
|
2200
|
+
const prompt = `Look at this screen and locate the UI element: "${elementQuery}".
|
|
1961
2201
|
Return ONLY a valid JSON object with the center point of this element in 0-1000 normalized coordinates:
|
|
1962
2202
|
{"point": [y, x]}
|
|
1963
2203
|
Where y is between 0 and 1000 (top to bottom) and x is between 0 and 1000 (left to right).
|
|
1964
2204
|
If not found, return {"point": null}. Output nothing else.`;
|
|
1965
|
-
const reply = await
|
|
2205
|
+
const reply = await callVisionAiWithFrame(liveFrame, prompt, 80);
|
|
1966
2206
|
if (reply) {
|
|
1967
2207
|
const match = reply.match(/\{[\s\S]*"point"[\s\S]*\}/);
|
|
1968
2208
|
if (match) {
|
|
@@ -1980,6 +2220,36 @@ If not found, return {"point": null}. Output nothing else.`;
|
|
|
1980
2220
|
}
|
|
1981
2221
|
catch { }
|
|
1982
2222
|
}
|
|
2223
|
+
else {
|
|
2224
|
+
const capture = await captureScreen(appName);
|
|
2225
|
+
if (capture.success && capture.details?.outPath && fs.existsSync(capture.details.outPath)) {
|
|
2226
|
+
screenWidth = capture.details.width || 1920;
|
|
2227
|
+
screenHeight = capture.details.height || 1080;
|
|
2228
|
+
try {
|
|
2229
|
+
const prompt = `Look at this screenshot and locate the UI element: "${elementQuery}".
|
|
2230
|
+
Return ONLY a valid JSON object with the center point of this element in 0-1000 normalized coordinates:
|
|
2231
|
+
{"point": [y, x]}
|
|
2232
|
+
Where y is between 0 and 1000 (top to bottom) and x is between 0 and 1000 (left to right).
|
|
2233
|
+
If not found, return {"point": null}. Output nothing else.`;
|
|
2234
|
+
const reply = await callVisionAi(capture.details.outPath, prompt, 80);
|
|
2235
|
+
if (reply) {
|
|
2236
|
+
const match = reply.match(/\{[\s\S]*"point"[\s\S]*\}/);
|
|
2237
|
+
if (match) {
|
|
2238
|
+
const parsed = JSON.parse(match[0]);
|
|
2239
|
+
if (Array.isArray(parsed.point) && parsed.point.length === 2) {
|
|
2240
|
+
const normY = Number(parsed.point[0]);
|
|
2241
|
+
const normX = Number(parsed.point[1]);
|
|
2242
|
+
if (!isNaN(normX) && !isNaN(normY)) {
|
|
2243
|
+
targetX = Math.round((normX / 1000) * screenWidth);
|
|
2244
|
+
targetY = Math.round((normY / 1000) * screenHeight);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
catch { }
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
1983
2253
|
if ((targetX === undefined || targetY === undefined) && process.platform === 'win32') {
|
|
1984
2254
|
const psScript = `
|
|
1985
2255
|
Add-Type -AssemblyName UIAutomationClient
|