fchek 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/bin/fchek.js +107 -0
- package/lib/api.js +110 -0
- package/lib/audit.js +211 -0
- package/lib/bench.js +248 -0
- package/lib/config.js +191 -0
- package/lib/context.js +356 -0
- package/lib/convention.js +526 -0
- package/lib/coverage.js +604 -0
- package/lib/db.js +135 -0
- package/lib/deps-check.js +264 -0
- package/lib/deps.js +374 -0
- package/lib/docker.js +84 -0
- package/lib/doctor.js +149 -0
- package/lib/dom.js +226 -0
- package/lib/fuzz.js +470 -0
- package/lib/git.js +290 -0
- package/lib/goto.js +544 -0
- package/lib/launch.js +182 -0
- package/lib/lint.js +624 -0
- package/lib/new_features.test.js +181 -0
- package/lib/output.js +46 -0
- package/lib/port.js +173 -0
- package/lib/process.js +228 -0
- package/lib/profile.js +453 -0
- package/lib/python.js +41 -0
- package/lib/race.js +186 -0
- package/lib/registry.js +179 -0
- package/lib/repl.js +135 -0
- package/lib/run.js +403 -0
- package/lib/screenshot.js +152 -0
- package/lib/secrets.js +257 -0
- package/lib/state.js +219 -0
- package/lib/test.js +471 -0
- package/lib/vuln.js +253 -0
- package/lib/watch.js +240 -0
- package/lib/winlog.js +123 -0
- package/package.json +27 -0
- package/skills/ACTIVATE.md +274 -0
- package/skills/README.md +163 -0
- package/skills/agent.md +444 -0
- package/skills/api.md +47 -0
- package/skills/bench.md +117 -0
- package/skills/context.md +116 -0
- package/skills/convention.md +143 -0
- package/skills/coverage.md +99 -0
- package/skills/csharp.md +97 -0
- package/skills/db.md +66 -0
- package/skills/deps-check.md +135 -0
- package/skills/deps.md +143 -0
- package/skills/docker.md +61 -0
- package/skills/dom.md +56 -0
- package/skills/fuzz.md +167 -0
- package/skills/goto.md +111 -0
- package/skills/lint.md +123 -0
- package/skills/port.md +57 -0
- package/skills/profile.md +91 -0
- package/skills/race.md +117 -0
- package/skills/repl.md +81 -0
- package/skills/rules.md +318 -0
- package/skills/run.md +135 -0
- package/skills/secrets.md +170 -0
- package/skills/security.md +360 -0
- package/skills/state.md +261 -0
- package/skills/vuln.md +57 -0
- package/skills/windows.md +320 -0
package/lib/launch.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* launch.js — start an application and wait for its window to appear
|
|
5
|
+
*
|
|
6
|
+
* Solves the problem: "did the app actually open and is the UI showing?"
|
|
7
|
+
* Returns PID, window title, position when the window is ready.
|
|
8
|
+
* Agent can then take a screenshot to verify UI state.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawnSync, spawn } = require('child_process');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const { output, ok, fail } = require('./output');
|
|
15
|
+
|
|
16
|
+
const HELP = `
|
|
17
|
+
fchek launch <exe> [--wait=<ms>] [--title=<partial>] [--args=<args>] [--kill-existing]
|
|
18
|
+
|
|
19
|
+
Launch an application and wait for its window to appear.
|
|
20
|
+
Use before fchek screenshot to verify the app started correctly.
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--wait=5000 Max wait time for window in ms (default: 10000)
|
|
24
|
+
--title=<text> Window title to wait for (default: exe basename)
|
|
25
|
+
--args=<args> Arguments to pass to the executable
|
|
26
|
+
--kill-existing Kill existing instances before launching
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
fchek launch bin/Vertex.exe
|
|
30
|
+
fchek launch bin/Vertex.exe --title=SpotlightWindow --wait=8000
|
|
31
|
+
fchek launch "C:/App/App.exe" --kill-existing
|
|
32
|
+
`.trim();
|
|
33
|
+
|
|
34
|
+
function runPowerShell(script, timeoutMs = 20000) {
|
|
35
|
+
return spawnSync('powershell', [
|
|
36
|
+
'-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script,
|
|
37
|
+
], { encoding: 'utf8', timeout: timeoutMs, windowsHide: false });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function run(args) {
|
|
41
|
+
if (args.length === 0 || args[0] === '--help') { console.log(HELP); return; }
|
|
42
|
+
|
|
43
|
+
if (process.platform !== 'win32') {
|
|
44
|
+
return output(fail('fchek launch is Windows-only.'));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const exePath = args[0];
|
|
48
|
+
const waitMs = parseInt((args.find(a => a.startsWith('--wait=')) || '--wait=10000').replace('--wait=', ''), 10);
|
|
49
|
+
const titleHint = (args.find(a => a.startsWith('--title=')) || '').replace('--title=', '') ||
|
|
50
|
+
path.basename(exePath, path.extname(exePath));
|
|
51
|
+
const exeArgs = (args.find(a => a.startsWith('--args=')) || '').replace('--args=', '') || '';
|
|
52
|
+
const killExisting = args.includes('--kill-existing');
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(exePath)) {
|
|
55
|
+
return output(fail(`Executable not found: ${exePath}`));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const absExe = path.resolve(exePath);
|
|
59
|
+
|
|
60
|
+
const script = `
|
|
61
|
+
Add-Type @"
|
|
62
|
+
using System;
|
|
63
|
+
using System.Runtime.InteropServices;
|
|
64
|
+
using System.Drawing;
|
|
65
|
+
public class WinCheck {
|
|
66
|
+
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hwnd, out RECT rect);
|
|
67
|
+
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hwnd);
|
|
68
|
+
public struct RECT { public int Left, Top, Right, Bottom; }
|
|
69
|
+
}
|
|
70
|
+
"@
|
|
71
|
+
|
|
72
|
+
${killExisting ? `
|
|
73
|
+
# Kill existing instances
|
|
74
|
+
$exeName = [System.IO.Path]::GetFileNameWithoutExtension(${JSON.stringify(absExe)})
|
|
75
|
+
Get-Process -Name $exeName -ErrorAction SilentlyContinue | Stop-Process -Force
|
|
76
|
+
Start-Sleep -Milliseconds 500
|
|
77
|
+
` : ''}
|
|
78
|
+
|
|
79
|
+
# Start the process
|
|
80
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
81
|
+
$psi.FileName = ${JSON.stringify(absExe)}
|
|
82
|
+
$psi.Arguments = ${JSON.stringify(exeArgs)}
|
|
83
|
+
$psi.UseShellExecute = $true
|
|
84
|
+
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
85
|
+
|
|
86
|
+
if ($null -eq $proc) {
|
|
87
|
+
Write-Output '{"launched":false,"error":"Failed to start process"}'
|
|
88
|
+
exit 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
$pid = $proc.Id
|
|
92
|
+
$waitMs = ${waitMs}
|
|
93
|
+
$step = 200
|
|
94
|
+
$elapsed = 0
|
|
95
|
+
$windowTitle = ""
|
|
96
|
+
$windowFound = $false
|
|
97
|
+
|
|
98
|
+
while ($elapsed -lt $waitMs) {
|
|
99
|
+
Start-Sleep -Milliseconds $step
|
|
100
|
+
$elapsed += $step
|
|
101
|
+
|
|
102
|
+
try { $proc.Refresh() } catch {}
|
|
103
|
+
|
|
104
|
+
# Check if process is still running
|
|
105
|
+
if ($proc.HasExited) {
|
|
106
|
+
Write-Output ('{"launched":true,"pid":' + $pid + ',"window_appeared":false,"error":"Process exited with code ' + $proc.ExitCode + '","elapsed_ms":' + $elapsed + '}')
|
|
107
|
+
exit 0
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Check for window
|
|
111
|
+
try {
|
|
112
|
+
$hwnd = $proc.MainWindowHandle
|
|
113
|
+
if ($hwnd -ne [IntPtr]::Zero) {
|
|
114
|
+
$title = $proc.MainWindowTitle
|
|
115
|
+
if ([string]::IsNullOrEmpty(${JSON.stringify(titleHint)}) -or $title -match [regex]::Escape(${JSON.stringify(titleHint)})) {
|
|
116
|
+
$rect = New-Object WinCheck+RECT
|
|
117
|
+
[WinCheck]::GetWindowRect($hwnd, [ref]$rect) | Out-Null
|
|
118
|
+
$w = $rect.Right - $rect.Left
|
|
119
|
+
$h = $rect.Bottom - $rect.Top
|
|
120
|
+
$windowTitle = $title
|
|
121
|
+
$windowFound = $true
|
|
122
|
+
Write-Output ('{"launched":true,"pid":' + $pid + ',"window_appeared":true,"window_title":"' + $title.Replace('"','\"') + '","x":' + $rect.Left + ',"y":' + $rect.Top + ',"width":' + $w + ',"height":' + $h + ',"elapsed_ms":' + $elapsed + '}')
|
|
123
|
+
exit 0
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
|
|
128
|
+
# Also search all processes for matching window title
|
|
129
|
+
$matching = Get-Process -ErrorAction SilentlyContinue | Where-Object {
|
|
130
|
+
$_.MainWindowTitle -match [regex]::Escape(${JSON.stringify(titleHint)}) -and $_.MainWindowHandle -ne [IntPtr]::Zero
|
|
131
|
+
}
|
|
132
|
+
if ($matching) {
|
|
133
|
+
$m = $matching[0]
|
|
134
|
+
$rect = New-Object WinCheck+RECT
|
|
135
|
+
[WinCheck]::GetWindowRect($m.MainWindowHandle, [ref]$rect) | Out-Null
|
|
136
|
+
$w = $rect.Right - $rect.Left
|
|
137
|
+
$h = $rect.Bottom - $rect.Top
|
|
138
|
+
Write-Output ('{"launched":true,"pid":' + $m.Id + ',"window_appeared":true,"window_title":"' + $m.MainWindowTitle.Replace('"','\"') + '","x":' + $rect.Left + ',"y":' + $rect.Top + ',"width":' + $w + ',"height":' + $h + ',"elapsed_ms":' + $elapsed + '}')
|
|
139
|
+
exit 0
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Timed out — window didn't appear
|
|
144
|
+
Write-Output ('{"launched":true,"pid":' + $pid + ',"window_appeared":false,"error":"Window did not appear within ' + $waitMs + 'ms","elapsed_ms":' + $elapsed + '}')
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
const res = runPowerShell(script, waitMs + 10000);
|
|
148
|
+
|
|
149
|
+
if (res.error) {
|
|
150
|
+
return output(fail(`PowerShell error: ${res.error.message}`));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const jsonLine = (res.stdout || '').split('\n').map(l => l.trim()).find(l => l.startsWith('{'));
|
|
154
|
+
|
|
155
|
+
if (!jsonLine) {
|
|
156
|
+
return output(fail(`launch output error: ${(res.stderr || res.stdout || '').slice(0, 500)}`));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let data;
|
|
160
|
+
try { data = JSON.parse(jsonLine); }
|
|
161
|
+
catch { return output(fail(`parse error: ${jsonLine.slice(0, 200)}`)); }
|
|
162
|
+
|
|
163
|
+
if (data.error && !data.launched) {
|
|
164
|
+
return output(fail(data.error));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
output(ok({
|
|
168
|
+
exe: absExe,
|
|
169
|
+
launched: data.launched,
|
|
170
|
+
pid: data.pid,
|
|
171
|
+
window_appeared: data.window_appeared,
|
|
172
|
+
window_title: data.window_title || null,
|
|
173
|
+
position: data.x != null ? { x: data.x, y: data.y, width: data.width, height: data.height } : null,
|
|
174
|
+
elapsed_ms: data.elapsed_ms,
|
|
175
|
+
error: data.error || null,
|
|
176
|
+
next_step: data.window_appeared
|
|
177
|
+
? `Window is visible. Run: fchek screenshot --window="${data.window_title || titleHint}"`
|
|
178
|
+
: 'Window did not appear. Check fchek winlog for errors.',
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = { run };
|