@etalon/cli 1.0.2 → 1.0.4
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 +6 -6
- package/dist/{chunk-Z6ZQZ5HI.js → chunk-YN7IXPMI.js} +1 -1
- package/dist/{consent-checker-B6J7GESG.js → consent-checker-QRPTMQWN.js} +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +169 -18
- package/dist/{policy-checker-J2WPHGU3.js → policy-checker-ONMTI7X2.js} +5 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -12,22 +12,22 @@ npm install -g etalon
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# Scan a website
|
|
15
|
-
|
|
15
|
+
etalon scan https://example.com
|
|
16
16
|
|
|
17
17
|
# JSON output
|
|
18
|
-
|
|
18
|
+
etalon scan https://example.com --format json
|
|
19
19
|
|
|
20
20
|
# SARIF for CI/CD (GitHub Code Scanning)
|
|
21
|
-
|
|
21
|
+
etalon scan https://example.com --format sarif
|
|
22
22
|
|
|
23
23
|
# Deep scan — scroll page, click consent dialogs
|
|
24
|
-
|
|
24
|
+
etalon scan https://example.com --deep
|
|
25
25
|
|
|
26
26
|
# Look up a single domain
|
|
27
|
-
|
|
27
|
+
etalon lookup google-analytics.com
|
|
28
28
|
|
|
29
29
|
# Registry stats
|
|
30
|
-
|
|
30
|
+
etalon info
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Options
|
|
@@ -11,7 +11,7 @@ var ETALON_VERSION = "1.0.0";
|
|
|
11
11
|
var DEFAULT_OPTIONS = {
|
|
12
12
|
deep: false,
|
|
13
13
|
timeout: 3e4,
|
|
14
|
-
waitForNetworkIdle:
|
|
14
|
+
waitForNetworkIdle: false,
|
|
15
15
|
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
|
16
16
|
viewport: { width: 1920, height: 1080 },
|
|
17
17
|
vendorDbPath: ""
|
|
@@ -74,8 +74,8 @@ async function checkConsent(url, options = {}) {
|
|
|
74
74
|
postRejectRequests.push(req);
|
|
75
75
|
}
|
|
76
76
|
});
|
|
77
|
-
await page.goto(url, { waitUntil: "
|
|
78
|
-
await page.waitForTimeout(
|
|
77
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout });
|
|
78
|
+
await page.waitForTimeout(3e3);
|
|
79
79
|
const { bannerDetected, bannerType, rejectSelector } = await detectBanner(page);
|
|
80
80
|
let rejectButtonFound = false;
|
|
81
81
|
let rejectClicked = false;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
scanSite
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YN7IXPMI.js";
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -20,7 +20,18 @@ import {
|
|
|
20
20
|
analyzeDataFlow,
|
|
21
21
|
toMermaid,
|
|
22
22
|
toTextSummary,
|
|
23
|
-
generatePolicy
|
|
23
|
+
generatePolicy,
|
|
24
|
+
AutoFixEngine,
|
|
25
|
+
applyContextScoring,
|
|
26
|
+
reportFalsePositive,
|
|
27
|
+
getFeedbackSummary,
|
|
28
|
+
isTelemetryEnabled,
|
|
29
|
+
enableTelemetry,
|
|
30
|
+
disableTelemetry,
|
|
31
|
+
recordAuditEvent,
|
|
32
|
+
analyzePatterns,
|
|
33
|
+
getLearningStats,
|
|
34
|
+
detectProjectContext
|
|
24
35
|
} from "@etalon/core";
|
|
25
36
|
|
|
26
37
|
// src/formatters/text.ts
|
|
@@ -778,14 +789,14 @@ var program = new Command();
|
|
|
778
789
|
program.name("etalon").description("ETALON \u2014 Open-source privacy auditor. Scan websites for trackers and GDPR compliance.").version(VERSION).hook("preAction", () => {
|
|
779
790
|
showBanner();
|
|
780
791
|
});
|
|
781
|
-
program.command("scan").description("Scan a website for third-party trackers").argument("<url>", "URL to scan").option("-f, --format <format>", "Output format: text, json, sarif", "text").option("-d, --deep", "Deep scan: scroll page, interact with consent dialogs", false).option("-t, --timeout <ms>", "Navigation timeout in milliseconds", "30000").option("--
|
|
792
|
+
program.command("scan").description("Scan a website for third-party trackers").argument("<url>", "URL to scan").option("-f, --format <format>", "Output format: text, json, sarif", "text").option("-d, --deep", "Deep scan: scroll page, interact with consent dialogs", false).option("-t, --timeout <ms>", "Navigation timeout in milliseconds", "30000").option("--idle", "Wait for network idle (slower but more thorough)").option("--config <path>", "Path to etalon.yaml config file").action(async (url, options) => {
|
|
782
793
|
const normalizedUrl = normalizeUrl(url);
|
|
783
794
|
const format = options.format ?? "text";
|
|
784
795
|
const config = loadConfig(options.config);
|
|
785
796
|
const scanOptions = {
|
|
786
797
|
deep: options.deep,
|
|
787
798
|
timeout: parseInt(options.timeout, 10),
|
|
788
|
-
waitForNetworkIdle: options.idle
|
|
799
|
+
waitForNetworkIdle: options.idle === true
|
|
789
800
|
};
|
|
790
801
|
if (config?.scan) {
|
|
791
802
|
if (config.scan.timeout && !options.timeout) scanOptions.timeout = config.scan.timeout;
|
|
@@ -841,14 +852,26 @@ program.command("audit").description("Scan a codebase for GDPR compliance (track
|
|
|
841
852
|
severity: options.severity,
|
|
842
853
|
includeBlame
|
|
843
854
|
});
|
|
855
|
+
const scoring = applyContextScoring(report.findings, dir);
|
|
856
|
+
report.findings = scoring.adjustedFindings;
|
|
857
|
+
if (scoring.adjustments.length > 0 && format === "text") {
|
|
858
|
+
spinner?.stop();
|
|
859
|
+
console.log(chalk4.bold(`
|
|
860
|
+
\u{1F3AF} Context: ${scoring.projectContext.industry} / ${scoring.projectContext.region}`));
|
|
861
|
+
console.log(chalk4.dim(` ${scoring.adjustments.length} finding(s) had severity adjusted based on context`));
|
|
862
|
+
for (const adj of scoring.adjustments.slice(0, 5)) {
|
|
863
|
+
console.log(` ${chalk4.dim(adj.finding_rule)}: ${adj.original_severity} \u2192 ${chalk4.yellow(adj.adjusted_severity)} (${adj.reason})`);
|
|
864
|
+
}
|
|
865
|
+
if (scoring.adjustments.length > 5) {
|
|
866
|
+
console.log(chalk4.dim(` ... and ${scoring.adjustments.length - 5} more`));
|
|
867
|
+
}
|
|
868
|
+
}
|
|
844
869
|
spinner?.stop();
|
|
845
870
|
if (autoFix) {
|
|
846
871
|
const patches = generatePatches(report.findings, dir);
|
|
847
|
-
if (patches.length
|
|
848
|
-
console.log(chalk4.yellow("\nNo auto-fixable issues found."));
|
|
849
|
-
} else {
|
|
872
|
+
if (patches.length > 0) {
|
|
850
873
|
console.log(chalk4.bold(`
|
|
851
|
-
\u{1F527} ${patches.length}
|
|
874
|
+
\u{1F527} ${patches.length} config fix(es):`));
|
|
852
875
|
for (const p of patches) {
|
|
853
876
|
console.log(` ${chalk4.dim(p.file)}:${p.line} \u2014 ${p.description}`);
|
|
854
877
|
console.log(` ${chalk4.red("- " + p.oldContent.trim())}`);
|
|
@@ -856,9 +879,58 @@ program.command("audit").description("Scan a codebase for GDPR compliance (track
|
|
|
856
879
|
}
|
|
857
880
|
const applied = applyPatches(patches, dir);
|
|
858
881
|
console.log(chalk4.green(`
|
|
859
|
-
\u2713 Applied ${applied} fix(es).`));
|
|
882
|
+
\u2713 Applied ${applied} config fix(es).`));
|
|
883
|
+
}
|
|
884
|
+
const engine = new AutoFixEngine();
|
|
885
|
+
const { readdirSync } = await import("fs");
|
|
886
|
+
const { join: joinPath } = await import("path");
|
|
887
|
+
const codeFiles = [];
|
|
888
|
+
const collectCodeFiles = (d) => {
|
|
889
|
+
try {
|
|
890
|
+
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
891
|
+
const full = joinPath(d, entry.name);
|
|
892
|
+
if (entry.isDirectory()) {
|
|
893
|
+
if (!["node_modules", ".git", "dist", "build", ".next", "__pycache__", "target"].includes(entry.name)) {
|
|
894
|
+
collectCodeFiles(full);
|
|
895
|
+
}
|
|
896
|
+
} else if (/\.(tsx?|jsx?|vue|svelte|html)$/.test(entry.name)) {
|
|
897
|
+
codeFiles.push(full);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
} catch {
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
collectCodeFiles(dir);
|
|
904
|
+
const suggestions = engine.scanFiles(codeFiles);
|
|
905
|
+
if (suggestions.length > 0) {
|
|
906
|
+
console.log(chalk4.bold(`
|
|
907
|
+
\u{1F6E1}\uFE0F ${suggestions.length} tracker consent fix(es) available:`));
|
|
908
|
+
for (const s of suggestions) {
|
|
909
|
+
console.log(` ${chalk4.cyan(s.tracker_name)} in ${chalk4.dim(s.location.file)}:${s.location.line}`);
|
|
910
|
+
console.log(` ${chalk4.dim(s.description)}`);
|
|
911
|
+
}
|
|
912
|
+
const hookPath = engine.generateConsentHook(dir);
|
|
913
|
+
console.log(chalk4.green(`
|
|
914
|
+
\u2713 Generated consent hook: ${chalk4.cyan(hookPath)}`));
|
|
915
|
+
const result = engine.applyAllFixes(suggestions);
|
|
916
|
+
console.log(chalk4.green(`\u2713 Applied ${result.applied} tracker consent fix(es).`));
|
|
917
|
+
if (result.failed > 0) {
|
|
918
|
+
console.log(chalk4.yellow(`\u26A0 ${result.failed} fix(es) could not be applied.`));
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (patches.length === 0 && suggestions.length === 0) {
|
|
922
|
+
console.log(chalk4.yellow("\nNo auto-fixable issues found."));
|
|
860
923
|
}
|
|
861
924
|
}
|
|
925
|
+
recordAuditEvent({
|
|
926
|
+
total_findings: report.findings.length,
|
|
927
|
+
critical: report.summary.critical,
|
|
928
|
+
high: report.summary.high,
|
|
929
|
+
medium: report.summary.medium,
|
|
930
|
+
framework: scoring.projectContext.industry,
|
|
931
|
+
industry: scoring.projectContext.industry,
|
|
932
|
+
region: scoring.projectContext.region
|
|
933
|
+
});
|
|
862
934
|
switch (format) {
|
|
863
935
|
case "json":
|
|
864
936
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -923,7 +995,7 @@ program.command("consent-check").description("Test if trackers fire before/after
|
|
|
923
995
|
const format = options.format ?? "text";
|
|
924
996
|
const spinner = format === "text" ? ora(`Checking consent on ${normalizedUrl}...`).start() : null;
|
|
925
997
|
try {
|
|
926
|
-
const { checkConsent } = await import("./consent-checker-
|
|
998
|
+
const { checkConsent } = await import("./consent-checker-QRPTMQWN.js");
|
|
927
999
|
const result = await checkConsent(normalizedUrl, {
|
|
928
1000
|
timeout: parseInt(options.timeout ?? "15000", 10)
|
|
929
1001
|
});
|
|
@@ -977,7 +1049,7 @@ program.command("policy-check").description("Cross-reference privacy policy text
|
|
|
977
1049
|
const format = options.format ?? "text";
|
|
978
1050
|
const spinner = format === "text" ? ora(`Analyzing privacy policy for ${normalizedUrl}...`).start() : null;
|
|
979
1051
|
try {
|
|
980
|
-
const { checkPolicy } = await import("./policy-checker-
|
|
1052
|
+
const { checkPolicy } = await import("./policy-checker-ONMTI7X2.js");
|
|
981
1053
|
const result = await checkPolicy(normalizedUrl, {
|
|
982
1054
|
timeout: parseInt(options.timeout ?? "30000", 10),
|
|
983
1055
|
policyUrl: options.policyUrl
|
|
@@ -1149,23 +1221,22 @@ Error: ${error.message}`);
|
|
|
1149
1221
|
program.command("data-flow").description("Map PII data flows through your codebase").argument("[dir]", "Directory to analyze", "./").option("-f, --format <format>", "Output format: text, mermaid, json", "text").action(async (dir, options) => {
|
|
1150
1222
|
const spinner = ora("Analyzing data flows...").start();
|
|
1151
1223
|
try {
|
|
1152
|
-
|
|
1224
|
+
const { readdirSync } = await import("fs");
|
|
1225
|
+
const { join: join3, relative } = await import("path");
|
|
1226
|
+
const files = [];
|
|
1227
|
+
const walk = (d) => {
|
|
1153
1228
|
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
1154
1229
|
const full = join3(d, entry.name);
|
|
1155
1230
|
if (entry.isDirectory()) {
|
|
1156
1231
|
if (!["node_modules", ".git", "dist", "build", ".next", "__pycache__"].includes(entry.name)) {
|
|
1157
|
-
|
|
1232
|
+
walk(full);
|
|
1158
1233
|
}
|
|
1159
1234
|
} else {
|
|
1160
1235
|
files.push(relative(dir, full));
|
|
1161
1236
|
}
|
|
1162
1237
|
}
|
|
1163
1238
|
};
|
|
1164
|
-
|
|
1165
|
-
const { readdirSync, statSync } = await import("fs");
|
|
1166
|
-
const { join: join3, relative } = await import("path");
|
|
1167
|
-
const files = [];
|
|
1168
|
-
walk2(dir);
|
|
1239
|
+
walk(dir);
|
|
1169
1240
|
const flow = analyzeDataFlow(files, dir);
|
|
1170
1241
|
spinner.stop();
|
|
1171
1242
|
switch (options.format) {
|
|
@@ -1187,4 +1258,84 @@ Error: ${error.message}`);
|
|
|
1187
1258
|
process.exit(2);
|
|
1188
1259
|
}
|
|
1189
1260
|
});
|
|
1261
|
+
program.command("report-fp").description("Report a false positive finding").requiredOption("--domain <domain>", "Domain that was incorrectly flagged").requiredOption("--rule <rule>", "Rule that triggered the false positive").option("--file <file>", "File where the false positive was found").option("--reason <reason>", "Why this is a false positive", "Not a tracker").action((options) => {
|
|
1262
|
+
const report = reportFalsePositive({
|
|
1263
|
+
domain: options.domain,
|
|
1264
|
+
rule: options.rule,
|
|
1265
|
+
file: options.file,
|
|
1266
|
+
reason: options.reason ?? "Not a tracker",
|
|
1267
|
+
suggested_action: "whitelist_domain"
|
|
1268
|
+
});
|
|
1269
|
+
console.log(chalk4.green(`\u2713 False positive reported: ${chalk4.bold(report.id)}`));
|
|
1270
|
+
console.log(chalk4.dim(` Domain: ${report.domain}`));
|
|
1271
|
+
console.log(chalk4.dim(` Rule: ${report.rule}`));
|
|
1272
|
+
console.log(chalk4.dim(` Reason: ${report.reason}`));
|
|
1273
|
+
console.log("");
|
|
1274
|
+
console.log(chalk4.dim("Reports help ETALON learn and reduce false positives over time."));
|
|
1275
|
+
});
|
|
1276
|
+
program.command("telemetry").description("Manage anonymous usage telemetry").argument("<action>", "enable, disable, or status").action((action) => {
|
|
1277
|
+
switch (action) {
|
|
1278
|
+
case "enable":
|
|
1279
|
+
enableTelemetry();
|
|
1280
|
+
console.log(chalk4.green("\u2713 Telemetry enabled."));
|
|
1281
|
+
console.log(chalk4.dim(" Anonymous usage data helps improve ETALON for everyone."));
|
|
1282
|
+
console.log(chalk4.dim(" No PII is ever collected. Set DO_NOT_TRACK=1 to override."));
|
|
1283
|
+
break;
|
|
1284
|
+
case "disable":
|
|
1285
|
+
disableTelemetry();
|
|
1286
|
+
console.log(chalk4.yellow("\u2713 Telemetry disabled."));
|
|
1287
|
+
break;
|
|
1288
|
+
case "status":
|
|
1289
|
+
console.log(`Telemetry: ${isTelemetryEnabled() ? chalk4.green("enabled") : chalk4.yellow("disabled")}`);
|
|
1290
|
+
break;
|
|
1291
|
+
default:
|
|
1292
|
+
console.error(chalk4.red(`Unknown action: ${action}. Use enable, disable, or status.`));
|
|
1293
|
+
process.exit(1);
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
program.command("intelligence").description("Show intelligence engine status and learned patterns").argument("[dir]", "Project directory for context detection", "./").action((dir) => {
|
|
1297
|
+
console.log(chalk4.bold("ETALON Intelligence Engine"));
|
|
1298
|
+
console.log(chalk4.dim("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
1299
|
+
console.log("");
|
|
1300
|
+
const ctx = detectProjectContext(dir);
|
|
1301
|
+
console.log(chalk4.bold("\u{1F3AF} Project Context"));
|
|
1302
|
+
console.log(` Industry: ${chalk4.cyan(ctx.industry)}`);
|
|
1303
|
+
console.log(` Region: ${chalk4.cyan(ctx.region)}`);
|
|
1304
|
+
console.log(` Data Sensitivity: ${chalk4.cyan(ctx.data_sensitivity)}`);
|
|
1305
|
+
if (ctx.detected_signals.length > 0) {
|
|
1306
|
+
console.log(chalk4.dim(" Signals:"));
|
|
1307
|
+
for (const s of ctx.detected_signals) {
|
|
1308
|
+
console.log(chalk4.dim(` \u2022 ${s}`));
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
console.log("");
|
|
1312
|
+
const stats = getLearningStats();
|
|
1313
|
+
console.log(chalk4.bold("\u{1F9E0} Learning Engine"));
|
|
1314
|
+
console.log(` Patterns learned: ${chalk4.cyan(String(stats.patterns_learned))}`);
|
|
1315
|
+
console.log(` Feedback processed: ${chalk4.cyan(String(stats.feedback_processed))}`);
|
|
1316
|
+
console.log(` Impact: ${chalk4.cyan(stats.accuracy_improvement)}`);
|
|
1317
|
+
console.log("");
|
|
1318
|
+
const feedback = getFeedbackSummary();
|
|
1319
|
+
if (feedback.total_reports > 0) {
|
|
1320
|
+
console.log(chalk4.bold("\u{1F4CA} False Positive Reports"));
|
|
1321
|
+
console.log(` Total reports: ${feedback.total_reports}`);
|
|
1322
|
+
if (feedback.suggested_whitelists.length > 0) {
|
|
1323
|
+
console.log(chalk4.dim(" Suggested whitelists (3+ reports):"));
|
|
1324
|
+
for (const d of feedback.suggested_whitelists) {
|
|
1325
|
+
console.log(chalk4.dim(` \u2022 ${d}`));
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
console.log("");
|
|
1329
|
+
}
|
|
1330
|
+
const learned = analyzePatterns();
|
|
1331
|
+
if (learned.length > 0) {
|
|
1332
|
+
console.log(chalk4.bold("\u{1F4DD} Learned Patterns"));
|
|
1333
|
+
for (const p of learned) {
|
|
1334
|
+
console.log(` ${chalk4.cyan(p.domain)} \u2014 ${p.suggested_action} (confidence: ${(p.confidence * 100).toFixed(0)}%)`);
|
|
1335
|
+
}
|
|
1336
|
+
console.log("");
|
|
1337
|
+
}
|
|
1338
|
+
console.log(chalk4.dim("Telemetry: ") + (isTelemetryEnabled() ? chalk4.green("enabled") : chalk4.yellow("disabled")));
|
|
1339
|
+
console.log("");
|
|
1340
|
+
});
|
|
1190
1341
|
program.parse();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
scanSite
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YN7IXPMI.js";
|
|
5
5
|
|
|
6
6
|
// src/policy-checker.ts
|
|
7
7
|
import { chromium } from "playwright";
|
|
@@ -46,7 +46,7 @@ async function findPolicyPage(page, siteUrl) {
|
|
|
46
46
|
const testUrl = `${baseUrl.origin}${path}`;
|
|
47
47
|
const response = await page.goto(testUrl, { waitUntil: "domcontentloaded", timeout: 1e4 });
|
|
48
48
|
if (response && response.status() >= 200 && response.status() < 400) {
|
|
49
|
-
const bodyText = await page.evaluate(() =>
|
|
49
|
+
const bodyText = await page.evaluate(() => document.body?.innerText?.trim() ?? "");
|
|
50
50
|
if (bodyText.length > 200) {
|
|
51
51
|
return testUrl;
|
|
52
52
|
}
|
|
@@ -82,13 +82,12 @@ async function findPolicyPage(page, siteUrl) {
|
|
|
82
82
|
return null;
|
|
83
83
|
}
|
|
84
84
|
async function extractPolicyText(page, policyUrl) {
|
|
85
|
-
await page.goto(policyUrl, { waitUntil: "
|
|
85
|
+
await page.goto(policyUrl, { waitUntil: "domcontentloaded", timeout: 2e4 });
|
|
86
86
|
await page.waitForTimeout(1e3);
|
|
87
87
|
const text = await page.evaluate(() => {
|
|
88
|
-
const
|
|
89
|
-
const elementsToRemove = doc.querySelectorAll("script, style, nav, header, footer, iframe");
|
|
88
|
+
const elementsToRemove = document.querySelectorAll("script, style, nav, header, footer, iframe");
|
|
90
89
|
elementsToRemove.forEach((el) => el.remove());
|
|
91
|
-
return
|
|
90
|
+
return document.body?.innerText ?? "";
|
|
92
91
|
});
|
|
93
92
|
return text.replace(/\s+/g, " ").trim();
|
|
94
93
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etalon/cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "ETALON
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "ETALON — Privacy audit tool for websites. Scan any site for trackers and GDPR compliance.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"ora": "^8.0.0",
|
|
24
24
|
"playwright": "^1.42.0",
|
|
25
25
|
"yaml": "^2.3.0",
|
|
26
|
-
"@etalon/core": "^1.0.
|
|
26
|
+
"@etalon/core": "^1.0.2"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
29
29
|
"privacy",
|