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.
Files changed (66) hide show
  1. package/README.md +64 -0
  2. package/bin/fchek.js +107 -0
  3. package/lib/api.js +110 -0
  4. package/lib/audit.js +211 -0
  5. package/lib/bench.js +248 -0
  6. package/lib/config.js +191 -0
  7. package/lib/context.js +356 -0
  8. package/lib/convention.js +526 -0
  9. package/lib/coverage.js +604 -0
  10. package/lib/db.js +135 -0
  11. package/lib/deps-check.js +264 -0
  12. package/lib/deps.js +374 -0
  13. package/lib/docker.js +84 -0
  14. package/lib/doctor.js +149 -0
  15. package/lib/dom.js +226 -0
  16. package/lib/fuzz.js +470 -0
  17. package/lib/git.js +290 -0
  18. package/lib/goto.js +544 -0
  19. package/lib/launch.js +182 -0
  20. package/lib/lint.js +624 -0
  21. package/lib/new_features.test.js +181 -0
  22. package/lib/output.js +46 -0
  23. package/lib/port.js +173 -0
  24. package/lib/process.js +228 -0
  25. package/lib/profile.js +453 -0
  26. package/lib/python.js +41 -0
  27. package/lib/race.js +186 -0
  28. package/lib/registry.js +179 -0
  29. package/lib/repl.js +135 -0
  30. package/lib/run.js +403 -0
  31. package/lib/screenshot.js +152 -0
  32. package/lib/secrets.js +257 -0
  33. package/lib/state.js +219 -0
  34. package/lib/test.js +471 -0
  35. package/lib/vuln.js +253 -0
  36. package/lib/watch.js +240 -0
  37. package/lib/winlog.js +123 -0
  38. package/package.json +27 -0
  39. package/skills/ACTIVATE.md +274 -0
  40. package/skills/README.md +163 -0
  41. package/skills/agent.md +444 -0
  42. package/skills/api.md +47 -0
  43. package/skills/bench.md +117 -0
  44. package/skills/context.md +116 -0
  45. package/skills/convention.md +143 -0
  46. package/skills/coverage.md +99 -0
  47. package/skills/csharp.md +97 -0
  48. package/skills/db.md +66 -0
  49. package/skills/deps-check.md +135 -0
  50. package/skills/deps.md +143 -0
  51. package/skills/docker.md +61 -0
  52. package/skills/dom.md +56 -0
  53. package/skills/fuzz.md +167 -0
  54. package/skills/goto.md +111 -0
  55. package/skills/lint.md +123 -0
  56. package/skills/port.md +57 -0
  57. package/skills/profile.md +91 -0
  58. package/skills/race.md +117 -0
  59. package/skills/repl.md +81 -0
  60. package/skills/rules.md +318 -0
  61. package/skills/run.md +135 -0
  62. package/skills/secrets.md +170 -0
  63. package/skills/security.md +360 -0
  64. package/skills/state.md +261 -0
  65. package/skills/vuln.md +57 -0
  66. package/skills/windows.md +320 -0
package/lib/vuln.js ADDED
@@ -0,0 +1,253 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+ const { output, ok, fail } = require('./output');
7
+
8
+ const HELP = `
9
+ fchek vuln [dir]
10
+
11
+ Scan project dependencies for vulnerabilities and typosquatting.
12
+ Automatically detects package.json, requirements.txt, and Cargo.toml.
13
+ Run npm audit / pip-audit / cargo audit, and runs a typosquatting check.
14
+ `.trim();
15
+
16
+ const POPULAR_PACKAGES = {
17
+ npm: [
18
+ 'react', 'react-dom', 'lodash', 'express', 'chalk', 'commander', 'tslib',
19
+ 'axios', 'moment', 'uuid', 'dotenv', 'fs-extra', 'async', 'prop-types',
20
+ 'classnames', 'jquery', 'glob', 'webpack', 'typescript', 'request',
21
+ 'cheerio', 'minimist', 'debug', 'bluebird', 'rxjs', 'vue', 'angular'
22
+ ],
23
+ pip: [
24
+ 'requests', 'numpy', 'pandas', 'flask', 'django', 'urllib3', 'six',
25
+ 'cryptography', 'jinja2', 'scipy', 'matplotlib', 'pytest', 'boto3',
26
+ 'click', 'pyyaml', 'black', 'ruff', 'pip', 'setuptools', 'wheel'
27
+ ],
28
+ cargo: [
29
+ 'serde', 'tokio', 'rand', 'reqwest', 'clap', 'syn', 'quote', 'log',
30
+ 'lazy_static', 'anyhow', 'futures', 'libc', 'hyper', 'regex', 'cargo'
31
+ ]
32
+ };
33
+
34
+ function levenshtein(a, b) {
35
+ const tmp = [];
36
+ for (let i = 0; i <= a.length; i++) {
37
+ tmp[i] = [i];
38
+ }
39
+ for (let j = 0; j <= b.length; j++) {
40
+ tmp[0][j] = j;
41
+ }
42
+ for (let i = 1; i <= a.length; i++) {
43
+ for (let j = 1; j <= b.length; j++) {
44
+ if (a[i - 1] === b[j - 1]) {
45
+ tmp[i][j] = tmp[i - 1][j - 1];
46
+ } else {
47
+ tmp[i][j] = Math.min(
48
+ tmp[i - 1][j - 1] + 1, // substitution
49
+ tmp[i][j - 1] + 1, // insertion
50
+ tmp[i - 1][j] + 1 // deletion
51
+ );
52
+ }
53
+ }
54
+ }
55
+ return tmp[a.length][b.length];
56
+ }
57
+
58
+ function checkTyposquatting(name, type) {
59
+ const popular = POPULAR_PACKAGES[type] || [];
60
+ const lowerName = name.toLowerCase();
61
+
62
+ // If exact match, it's fine
63
+ if (popular.includes(lowerName)) return null;
64
+
65
+ for (const pop of popular) {
66
+ const dist = levenshtein(lowerName, pop);
67
+ // If name is very close (distance 1 or 2) but not identical, warn
68
+ if (dist > 0 && dist <= 2 && Math.abs(lowerName.length - pop.length) <= 2) {
69
+ return {
70
+ package: name,
71
+ type,
72
+ suspectedTyposquatOf: pop,
73
+ distance: dist
74
+ };
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+
80
+ async function run(args) {
81
+ if (args.includes('--help') || args.includes('-h')) {
82
+ console.log(HELP);
83
+ return;
84
+ }
85
+
86
+ const targetDir = args.find(a => !a.startsWith('-')) || '.';
87
+ const resolvedDir = path.resolve(targetDir);
88
+
89
+ if (!fs.existsSync(resolvedDir)) {
90
+ output(fail(`Directory does not exist: ${targetDir}`, 'vuln'));
91
+ return;
92
+ }
93
+
94
+ const results = {
95
+ packages_checked: 0,
96
+ vulnerabilities: [],
97
+ typosquatting_warnings: [],
98
+ audit_ran: []
99
+ };
100
+
101
+ // Node (package.json)
102
+ const pkgPath = path.join(resolvedDir, 'package.json');
103
+ if (fs.existsSync(pkgPath)) {
104
+ try {
105
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
106
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
107
+
108
+ for (const name of Object.keys(deps)) {
109
+ results.packages_checked++;
110
+ const warn = checkTyposquatting(name, 'npm');
111
+ if (warn) results.typosquatting_warnings.push(warn);
112
+ }
113
+
114
+ // Try running npm audit
115
+ try {
116
+ const auditOut = execSync('npm audit --json', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], cwd: resolvedDir });
117
+ const auditJson = JSON.parse(auditOut);
118
+ results.audit_ran.push('npm');
119
+ if (auditJson.vulnerabilities) {
120
+ for (const [name, vuln] of Object.entries(auditJson.vulnerabilities)) {
121
+ results.vulnerabilities.push({
122
+ package: name,
123
+ severity: vuln.severity,
124
+ via: vuln.via,
125
+ effects: vuln.effects
126
+ });
127
+ }
128
+ }
129
+ } catch (auditErr) {
130
+ // npm audit exits with non-zero if vulnerabilities are found
131
+ results.audit_ran.push('npm');
132
+ try {
133
+ const auditJson = JSON.parse(auditErr.stdout);
134
+ if (auditJson.vulnerabilities) {
135
+ for (const [name, vuln] of Object.entries(auditJson.vulnerabilities)) {
136
+ results.vulnerabilities.push({
137
+ package: name,
138
+ severity: vuln.severity,
139
+ via: vuln.via,
140
+ effects: vuln.effects
141
+ });
142
+ }
143
+ }
144
+ } catch {
145
+ // If JSON parse failed, it was a real execution error or npm not installed
146
+ }
147
+ }
148
+ } catch (err) {
149
+ // Ignore parse/read errors
150
+ }
151
+ }
152
+
153
+ // Python (requirements.txt)
154
+ const reqPath = path.join(resolvedDir, 'requirements.txt');
155
+ if (fs.existsSync(reqPath)) {
156
+ try {
157
+ const content = fs.readFileSync(reqPath, 'utf8');
158
+ const lines = content.split('\n');
159
+ for (const line of lines) {
160
+ const trimmed = line.trim();
161
+ if (!trimmed || trimmed.startsWith('#')) continue;
162
+ // Match package name (before ==, >=, etc.)
163
+ const match = trimmed.match(/^([a-zA-Z0-9_\-]+)/);
164
+ if (match) {
165
+ const name = match[1];
166
+ results.packages_checked++;
167
+ const warn = checkTyposquatting(name, 'pip');
168
+ if (warn) results.typosquatting_warnings.push(warn);
169
+ }
170
+ }
171
+
172
+ // Try running pip-audit if installed
173
+ try {
174
+ const auditOut = execSync('pip-audit --format json', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], cwd: resolvedDir });
175
+ const auditJson = JSON.parse(auditOut);
176
+ results.audit_ran.push('pip');
177
+ if (auditJson.dependencies) {
178
+ for (const dep of auditJson.dependencies) {
179
+ if (dep.vulns && dep.vulns.length > 0) {
180
+ for (const v of dep.vulns) {
181
+ results.vulnerabilities.push({
182
+ package: dep.name,
183
+ id: v.id,
184
+ severity: 'unknown',
185
+ description: v.description
186
+ });
187
+ }
188
+ }
189
+ }
190
+ }
191
+ } catch {
192
+ // pip-audit not installed or failed
193
+ }
194
+ } catch (err) {
195
+ // Ignore
196
+ }
197
+ }
198
+
199
+ // Rust (Cargo.toml)
200
+ const cargoPath = path.join(resolvedDir, 'Cargo.toml');
201
+ if (fs.existsSync(cargoPath)) {
202
+ try {
203
+ const content = fs.readFileSync(cargoPath, 'utf8');
204
+ // Simple parser for dependencies block
205
+ const lines = content.split('\n');
206
+ let inDeps = false;
207
+ for (const line of lines) {
208
+ const trimmed = line.trim();
209
+ if (trimmed.startsWith('[dependencies]') || trimmed.startsWith('[dev-dependencies]')) {
210
+ inDeps = true;
211
+ continue;
212
+ } else if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
213
+ inDeps = false;
214
+ }
215
+
216
+ if (inDeps && trimmed && !trimmed.startsWith('#')) {
217
+ const eqIdx = trimmed.indexOf('=');
218
+ if (eqIdx !== -1) {
219
+ const name = trimmed.slice(0, eqIdx).trim();
220
+ results.packages_checked++;
221
+ const warn = checkTyposquatting(name, 'cargo');
222
+ if (warn) results.typosquatting_warnings.push(warn);
223
+ }
224
+ }
225
+ }
226
+
227
+ // Try running cargo audit if available
228
+ try {
229
+ const auditOut = execSync('cargo audit --json', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], cwd: resolvedDir });
230
+ const auditJson = JSON.parse(auditOut);
231
+ results.audit_ran.push('cargo');
232
+ if (auditJson.vulnerabilities && auditJson.vulnerabilities.list) {
233
+ for (const v of auditJson.vulnerabilities.list) {
234
+ results.vulnerabilities.push({
235
+ package: v.package.name,
236
+ id: v.advisory.id,
237
+ severity: 'unknown',
238
+ description: v.advisory.description
239
+ });
240
+ }
241
+ }
242
+ } catch {
243
+ // cargo-audit not available
244
+ }
245
+ } catch (err) {
246
+ // Ignore
247
+ }
248
+ }
249
+
250
+ output(ok(results, 'vuln'));
251
+ }
252
+
253
+ module.exports = { run };
package/lib/watch.js ADDED
@@ -0,0 +1,240 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * watch.js — watch files and auto-rebuild/restart on changes
5
+ *
6
+ * Solves WPF/C# pain: change XAML → app auto-rebuilds → re-launches.
7
+ * No manual rebuild steps.
8
+ *
9
+ * Uses Node.js fs.watch (built-in, no external deps).
10
+ * On change: runs build command, then optionally restarts app.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { spawnSync, spawn } = require('child_process');
16
+ const { output, ok, fail } = require('./output');
17
+
18
+ const HELP = `
19
+ fchek watch <dir> [--cmd=<build_cmd>] [--ext=cs,xaml] [--launch=<exe>] [--debounce=1000]
20
+
21
+ Watch files for changes and auto-rebuild. For WPF: change XAML → rebuilds → relaunches.
22
+
23
+ Options:
24
+ --cmd=<command> Build command to run on change (default: dotnet build)
25
+ --ext=cs,xaml File extensions to watch (default: cs,xaml,js,ts,py,rs)
26
+ --launch=<exe> Re-launch this exe after successful build
27
+ --debounce=1000 Wait Nms after last change before rebuilding (default: 1000)
28
+ --once Build once and exit (no watch loop)
29
+
30
+ Examples:
31
+ fchek watch .
32
+ fchek watch . --ext=cs,xaml --cmd="dotnet build"
33
+ fchek watch . --cmd="dotnet build" --launch=bin/Debug/net8.0-windows/Vertex.exe
34
+ fchek watch src/ --ext=py --cmd="python -m pytest" --debounce=500
35
+ `.trim();
36
+
37
+ function runCommand(cmd, cwd) {
38
+ const parts = cmd.split(/\s+/);
39
+ const res = spawnSync(parts[0], parts.slice(1), {
40
+ encoding: 'utf8',
41
+ cwd: cwd || process.cwd(),
42
+ timeout: 120000,
43
+ windowsHide: true,
44
+ });
45
+ return {
46
+ success: res.status === 0,
47
+ exit_code: res.status,
48
+ stdout: (res.stdout || '').slice(-2000),
49
+ stderr: (res.stderr || '').slice(-1000),
50
+ error: res.error?.message || null,
51
+ };
52
+ }
53
+
54
+ function killProcess(name) {
55
+ if (process.platform !== 'win32') return;
56
+ spawnSync('powershell', [
57
+ '-NoProfile', '-NonInteractive', '-Command',
58
+ `Get-Process -Name "${name}" -ErrorAction SilentlyContinue | Stop-Process -Force`,
59
+ ], { timeout: 5000, windowsHide: true });
60
+ }
61
+
62
+ function launchProcess(exe) {
63
+ const abs = path.resolve(exe);
64
+ if (!fs.existsSync(abs)) return { error: `Executable not found: ${abs}` };
65
+ const child = spawn(abs, [], {
66
+ detached: true,
67
+ stdio: 'ignore',
68
+ windowsHide: false,
69
+ });
70
+ child.unref();
71
+ return { pid: child.pid };
72
+ }
73
+
74
+ async function run(args) {
75
+ if (args.length === 0 || args[0] === '--help') { console.log(HELP); return; }
76
+
77
+ const dir = args.find(a => !a.startsWith('--')) || '.';
78
+ const cmd = (args.find(a => a.startsWith('--cmd=')) || '').replace('--cmd=', '') || autoDetectCmd(dir);
79
+ const extArg = (args.find(a => a.startsWith('--ext=')) || '--ext=cs,xaml,js,ts,py,rs,go,cpp,c').replace('--ext=', '');
80
+ const launchExe = (args.find(a => a.startsWith('--launch=')) || '').replace('--launch=', '') || null;
81
+ const debounce = parseInt((args.find(a => a.startsWith('--debounce=')) || '--debounce=1000').replace('--debounce=', ''), 10);
82
+ const once = args.includes('--once');
83
+
84
+ if (!fs.existsSync(dir)) {
85
+ return output(fail(`Directory not found: ${dir}`));
86
+ }
87
+
88
+ const exts = extArg.split(',').map(e => `.${e.replace(/^\./, '')}`);
89
+ const absDir = path.resolve(dir);
90
+
91
+ if (!cmd) {
92
+ return output(fail(
93
+ 'No build command detected. Use --cmd="dotnet build" or --cmd="npm run build"'
94
+ ));
95
+ }
96
+
97
+ // Run once and exit
98
+ if (once) {
99
+ console.error(`[fchek watch] Building: ${cmd}`);
100
+ const result = runCommand(cmd, absDir);
101
+ output(ok({
102
+ mode: 'once',
103
+ dir: absDir,
104
+ cmd,
105
+ ...result,
106
+ built_at: new Date().toISOString(),
107
+ }));
108
+ return;
109
+ }
110
+
111
+ // Watch mode — output status line per rebuild, then stay running
112
+ console.error(`[fchek watch] Watching: ${absDir}`);
113
+ console.error(`[fchek watch] Extensions: ${exts.join(', ')}`);
114
+ console.error(`[fchek watch] Command: ${cmd}`);
115
+ if (launchExe) console.error(`[fchek watch] Launch: ${launchExe}`);
116
+ console.error(`[fchek watch] Debounce: ${debounce}ms\n`);
117
+
118
+ let debounceTimer = null;
119
+ let building = false;
120
+ let buildCount = 0;
121
+
122
+ const doBuild = () => {
123
+ if (building) return;
124
+ building = true;
125
+ buildCount++;
126
+
127
+ const ts = new Date().toLocaleTimeString();
128
+ console.error(`[${ts}] Change detected — building...`);
129
+
130
+ const result = runCommand(cmd, absDir);
131
+
132
+ if (result.success) {
133
+ console.error(`[${ts}] ✓ Build #${buildCount} succeeded`);
134
+
135
+ if (launchExe) {
136
+ const exeName = path.basename(launchExe, path.extname(launchExe));
137
+ killProcess(exeName);
138
+ const launched = launchProcess(launchExe);
139
+ if (launched.error) {
140
+ console.error(`[${ts}] ✗ Launch failed: ${launched.error}`);
141
+ } else {
142
+ console.error(`[${ts}] ✓ Launched PID ${launched.pid}`);
143
+ }
144
+ }
145
+
146
+ // Output JSON for agent to read
147
+ console.log(JSON.stringify({
148
+ status: 'ok',
149
+ command: 'watch',
150
+ data: {
151
+ build: buildCount,
152
+ success: true,
153
+ cmd,
154
+ built_at: new Date().toISOString(),
155
+ stdout: result.stdout,
156
+ launched: launchExe ? true : null,
157
+ },
158
+ }));
159
+ } else {
160
+ console.error(`[${ts}] ✗ Build #${buildCount} failed (exit ${result.exit_code})`);
161
+ if (result.stderr) console.error(result.stderr.slice(0, 500));
162
+
163
+ console.log(JSON.stringify({
164
+ status: 'ok',
165
+ command: 'watch',
166
+ data: {
167
+ build: buildCount,
168
+ success: false,
169
+ cmd,
170
+ exit_code: result.exit_code,
171
+ stderr: result.stderr,
172
+ built_at: new Date().toISOString(),
173
+ },
174
+ }));
175
+ }
176
+
177
+ building = false;
178
+ };
179
+
180
+ // Initial build
181
+ doBuild();
182
+
183
+ // Watch for changes
184
+ const watchedDirs = new Set([absDir]);
185
+
186
+ // Also watch subdirs (fs.watch is not recursive on all platforms)
187
+ function addWatchers(d, depth = 0) {
188
+ if (depth > 5) return;
189
+ try {
190
+ for (const name of fs.readdirSync(d)) {
191
+ if (['node_modules', '.git', 'bin', 'obj', 'dist', 'target', '__pycache__'].includes(name)) continue;
192
+ const full = path.join(d, name);
193
+ try {
194
+ if (fs.statSync(full).isDirectory() && !watchedDirs.has(full)) {
195
+ watchedDirs.add(full);
196
+ addWatchers(full, depth + 1);
197
+ }
198
+ } catch {}
199
+ }
200
+ } catch {}
201
+ }
202
+ addWatchers(absDir);
203
+
204
+ for (const watchDir of watchedDirs) {
205
+ try {
206
+ fs.watch(watchDir, (eventType, filename) => {
207
+ if (!filename) return;
208
+ const ext = path.extname(filename).toLowerCase();
209
+ if (!exts.includes(ext)) return;
210
+
211
+ clearTimeout(debounceTimer);
212
+ debounceTimer = setTimeout(doBuild, debounce);
213
+ });
214
+ } catch {}
215
+ }
216
+
217
+ console.error('[fchek watch] Press Ctrl+C to stop.\n');
218
+
219
+ // Keep process alive
220
+ process.on('SIGINT', () => {
221
+ console.error('\n[fchek watch] Stopped.');
222
+ process.exit(0);
223
+ });
224
+
225
+ await new Promise(() => {}); // Never resolves — keeps running
226
+ }
227
+
228
+ function autoDetectCmd(dir) {
229
+ const abs = path.resolve(dir);
230
+ const files = (() => { try { return fs.readdirSync(abs); } catch { return []; } })();
231
+
232
+ if (files.some(f => f.endsWith('.csproj') || f.endsWith('.sln'))) return 'dotnet build';
233
+ if (files.includes('Cargo.toml')) return 'cargo build';
234
+ if (files.includes('package.json')) return 'npm run build';
235
+ if (files.includes('go.mod')) return 'go build ./...';
236
+ if (files.some(f => f.endsWith('.py'))) return 'python -m py_compile *.py';
237
+ return null;
238
+ }
239
+
240
+ module.exports = { run };
package/lib/winlog.js ADDED
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * winlog.js — read Windows Event Viewer via PowerShell
5
+ *
6
+ * Reads Application, System, or custom event logs.
7
+ * Filters by source name, level (Error/Warning/Info), time range.
8
+ * Agent uses this to see runtime crashes, silent failures, startup errors.
9
+ */
10
+
11
+ const { spawnSync } = require('child_process');
12
+ const { output, ok, fail } = require('./output');
13
+
14
+ const HELP = `
15
+ fchek winlog [--source=Application|System|Security] [--filter=<name>] [--last=50] [--level=Error|Warning|Info] [--since=<minutes>]
16
+
17
+ Read Windows Event Viewer logs. Find crashes and silent failures your app generates.
18
+
19
+ Options:
20
+ --source=Application Log source (default: Application)
21
+ --filter=<name> Filter by source/provider name (e.g. "Vertex", ".NET Runtime")
22
+ --last=50 Number of recent events (default: 50)
23
+ --level=Error Filter by level: Error, Warning, Information, Critical
24
+ --since=60 Events from last N minutes
25
+
26
+ Examples:
27
+ fchek winlog
28
+ fchek winlog --source=Application --filter=Vertex
29
+ fchek winlog --level=Error --since=30
30
+ fchek winlog --source=System --level=Critical
31
+ `.trim();
32
+
33
+ function runPowerShell(script, timeoutMs = 30000) {
34
+ return spawnSync('powershell', [
35
+ '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script,
36
+ ], { encoding: 'utf8', timeout: timeoutMs, windowsHide: true });
37
+ }
38
+
39
+ async function run(args) {
40
+ if (args[0] === '--help') { console.log(HELP); return; }
41
+
42
+ if (process.platform !== 'win32') {
43
+ return output(fail('fchek winlog is Windows-only.'));
44
+ }
45
+
46
+ const source = (args.find(a => a.startsWith('--source=')) || '--source=Application').replace('--source=', '');
47
+ const filter = (args.find(a => a.startsWith('--filter=')) || '').replace('--filter=', '') || null;
48
+ const last = parseInt((args.find(a => a.startsWith('--last=')) || '--last=50').replace('--last=', ''), 10);
49
+ const level = (args.find(a => a.startsWith('--level=')) || '').replace('--level=', '') || null;
50
+ const sinceMin = parseInt((args.find(a => a.startsWith('--since=')) || '--since=0').replace('--since=', ''), 10) || null;
51
+
52
+ // Build where clause
53
+ const whereParts = [];
54
+ if (filter) whereParts.push(`$_.ProviderName -match ${JSON.stringify(filter)}`);
55
+ if (level) whereParts.push(`$_.LevelDisplayName -eq ${JSON.stringify(level)}`);
56
+ if (sinceMin) whereParts.push(`$_.TimeCreated -ge (Get-Date).AddMinutes(-${sinceMin})`);
57
+
58
+ const whereClause = whereParts.length > 0
59
+ ? `| Where-Object { ${whereParts.join(' -and ')} }`
60
+ : '';
61
+
62
+ const script = `
63
+ $ErrorActionPreference = 'SilentlyContinue'
64
+ try {
65
+ $events = Get-WinEvent -LogName ${JSON.stringify(source)} -MaxEvents ${last * 3} ${whereClause} -ErrorAction SilentlyContinue |
66
+ Select-Object -First ${last} |
67
+ ForEach-Object {
68
+ @{
69
+ time = $_.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss")
70
+ level = $_.LevelDisplayName
71
+ provider = $_.ProviderName
72
+ id = $_.Id
73
+ message = ($_.Message -replace '"', "'") -replace "[\\r\\n]+", " " | ForEach-Object { $_.Substring(0, [Math]::Min($_.Length, 300)) }
74
+ }
75
+ }
76
+
77
+ $json = $events | ConvertTo-Json -Depth 3 -Compress
78
+ if ($null -eq $json) { $json = "[]" }
79
+ Write-Output $json
80
+ } catch {
81
+ Write-Output ('{"error":"' + $_.Exception.Message.Replace('"',"'") + '"}')
82
+ }
83
+ `;
84
+
85
+ const res = runPowerShell(script);
86
+
87
+ if (res.error) {
88
+ return output(fail(`PowerShell error: ${res.error.message}`));
89
+ }
90
+
91
+ const raw = (res.stdout || '').trim();
92
+ if (!raw) {
93
+ return output(ok({ source, filter, events: [], count: 0, note: 'No events found or log empty.' }));
94
+ }
95
+
96
+ let events;
97
+ try {
98
+ const parsed = JSON.parse(raw);
99
+ // ConvertTo-Json wraps single item as object, multiple as array
100
+ events = Array.isArray(parsed) ? parsed : (parsed.error ? null : [parsed]);
101
+ if (!events) return output(fail(parsed.error));
102
+ } catch {
103
+ return output(fail(`Parse error: ${raw.slice(0, 300)}`));
104
+ }
105
+
106
+ // Categorize
107
+ const errors = events.filter(e => e.level === 'Error' || e.level === 'Critical');
108
+ const warnings = events.filter(e => e.level === 'Warning');
109
+
110
+ output(ok({
111
+ source,
112
+ filter: filter || null,
113
+ level: level || null,
114
+ since_min: sinceMin || null,
115
+ count: events.length,
116
+ errors: errors.length,
117
+ warnings: warnings.length,
118
+ events,
119
+ verdict: errors.length > 0 ? 'errors_found' : warnings.length > 0 ? 'warnings_found' : 'clean',
120
+ }));
121
+ }
122
+
123
+ module.exports = { run };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "fchek",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool for developers and AI agents: profiling, sanitizers, LSP, coverage, fuzzing and more",
5
+ "bin": {
6
+ "fchek": "./bin/fchek.js"
7
+ },
8
+ "main": "./bin/fchek.js",
9
+ "scripts": {
10
+ "start": "node bin/fchek.js",
11
+ "build": "npx @yao-pkg/pkg . --target node22-win-x64 --output dist/fchek.exe --compress GZip",
12
+ "build:linux": "npx @yao-pkg/pkg . --target node22-linux-x64 --output dist/fchek --compress GZip",
13
+ "install-global": "npm install -g ."
14
+ },
15
+ "pkg": {
16
+ "targets": ["node22-win-x64"],
17
+ "scripts": [
18
+ "bin/**/*.js",
19
+ "lib/**/*.js"
20
+ ],
21
+ "outputPath": "dist"
22
+ },
23
+ "keywords": ["cli", "profiler", "sanitizer", "lsp", "ai-tools"],
24
+ "license": "MIT",
25
+ "files": ["bin/", "lib/", "skills/", "README.md"],
26
+ "engines": { "node": ">=18" }
27
+ }