@cloudcreate/adsense-check 1.0.1 → 1.2.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/dist/cli.js CHANGED
@@ -1,108 +1,196 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- check
4
- } from "./chunk-V2YZ36NU.js";
3
+ BrowserManager,
4
+ analyzeSiteTopic,
5
+ analyzeWithAI,
6
+ check,
7
+ computePageAiScore,
8
+ detectSiteType,
9
+ fetchPage,
10
+ getSupportedLangs,
11
+ isValidLang,
12
+ t
13
+ } from "./chunk-SES7MRFF.js";
5
14
 
6
15
  // src/cli.ts
7
16
  import "dotenv/config";
8
17
  import { Command } from "commander";
9
18
  import chalk2 from "chalk";
10
19
  import { mkdirSync, writeFileSync } from "fs";
11
- import { join, dirname } from "path";
12
- import { fileURLToPath } from "url";
20
+ import { join } from "path";
13
21
 
14
22
  // src/reporter.ts
15
23
  import chalk from "chalk";
16
24
  import figures from "figures";
17
- var STATUS_ICONS = {
25
+ var ICONS = {
18
26
  pass: chalk.green(figures.tick),
19
27
  warn: chalk.yellow(figures.warning),
20
28
  fail: chalk.red(figures.cross),
21
29
  skip: chalk.gray("-")
22
30
  };
23
- var STATUS_LABELS = {
31
+ var LABELS = {
24
32
  pass: chalk.green("PASS"),
25
33
  warn: chalk.yellow("WARN"),
26
34
  fail: chalk.red("FAIL"),
27
35
  skip: chalk.gray("SKIP")
28
36
  };
29
- function getStatusSummary(report) {
30
- if (report.failed > 0) {
31
- return chalk.red.bold(`NOT READY \u2014 ${report.failed} \u9879\u5931\u8D25\u9700\u8981\u4FEE\u590D`);
32
- }
33
- if (report.warned > 0) {
34
- return chalk.yellow.bold(`MOSTLY READY \u2014 \u4FEE\u590D ${report.warned} \u9879\u8B66\u544A\u540E\u53EF\u63D0\u4EA4\u5BA1\u6838`);
35
- }
36
- return chalk.green.bold("READY \u2014 \u53EF\u4EE5\u63D0\u4EA4 AdSense \u5BA1\u6838");
37
+ function renderBar(score, max, width = 20) {
38
+ const ratio = max > 0 ? score / max : 0;
39
+ const filled = Math.round(ratio * width);
40
+ const empty = width - filled;
41
+ const color = ratio >= 0.8 ? chalk.green : ratio >= 0.5 ? chalk.yellow : chalk.red;
42
+ return color("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
43
+ }
44
+ function categoryScore(cat) {
45
+ if (cat.items.length === 0) return 100;
46
+ const earned = cat.items.reduce((s, i) => {
47
+ if (i.status === "pass") return s + 100;
48
+ if (i.status === "warn") return s + 40;
49
+ return s;
50
+ }, 0);
51
+ return Math.round(earned / cat.items.length);
37
52
  }
38
53
  function renderTerminalReport(report) {
39
- const lines = [];
54
+ const lang = report.lang;
55
+ const typeKey = `detector.type.${report.siteType}`;
56
+ const typeLabel = t(typeKey, lang);
57
+ const confidenceLabel = report.siteTypeConfidence === "high" ? "" : ` (${report.siteTypeConfidence})`;
58
+ const lines = [
59
+ "",
60
+ chalk.bold.cyan(` ${t("report.title", lang)}`),
61
+ chalk.gray(` URL: ${report.url}`),
62
+ chalk.gray(` Time: ${report.timestamp}`),
63
+ chalk.gray(` Site type: ${typeLabel}${confidenceLabel}`)
64
+ ];
65
+ if (report.siteTopic) {
66
+ lines.push(chalk.gray(` Topic: ${report.siteTopic.topic} \u2014 ${report.siteTopic.description}`));
67
+ }
68
+ if (report.samplingInfo) {
69
+ const s = report.samplingInfo;
70
+ const confColor = s.confidence === "high" ? chalk.green : s.confidence === "medium" ? chalk.yellow : chalk.red;
71
+ lines.push(chalk.gray(` Pages: ${s.totalDiscovered} total, ${s.recentCount} recent (6mo), ${s.sampledCount} sampled (${s.samplePct}%) ${confColor(s.confidence + " confidence")}`));
72
+ }
73
+ if (report.siteType === "unsupported") {
74
+ lines.push("");
75
+ lines.push(chalk.red.bold(` ${t("topic.unsupported_warning", lang, { type: report.siteTopic?.topic ?? "unknown" })}`));
76
+ }
40
77
  lines.push("");
41
- lines.push(chalk.bold.cyan(" AdSense Checklist Report"));
42
- lines.push(chalk.gray(` Website: ${report.url}`));
43
- lines.push(chalk.gray(` Checked: ${report.timestamp}`));
78
+ const scoreColor = report.compositeScore >= 80 ? chalk.green.bold : report.compositeScore >= 50 ? chalk.yellow.bold : chalk.red.bold;
79
+ lines.push(chalk.bold(` ${t("report.composite_score", lang)}: `) + scoreColor(`${report.compositeScore}/100`));
44
80
  lines.push("");
45
- for (const category of report.categories) {
46
- lines.push(chalk.bold(` ${category.name}`));
47
- for (const item of category.items) {
48
- const icon = STATUS_ICONS[item.status];
49
- const label = STATUS_LABELS[item.status];
50
- lines.push(` ${icon} [${label}] ${item.message}`);
51
- if (item.detail) {
52
- lines.push(chalk.gray(` ${item.detail}`));
53
- }
81
+ const hardColor = report.hardStatus === "ready" ? chalk.green : report.hardStatus === "warn" ? chalk.yellow : chalk.red;
82
+ const hardLabel = report.hardStatus === "ready" ? "PASS" : report.hardStatus === "warn" ? "WARN" : "FAIL";
83
+ lines.push(chalk.bold(` \u250C\u2500 ${t("report.hard_requirements", lang)} `) + chalk.gray("\u2500".repeat(Math.max(0, 40 - t("report.hard_requirements", lang).length))) + ` ${hardColor.bold(hardLabel)}`);
84
+ for (const cat of report.hardCategories) {
85
+ const catScore = categoryScore(cat);
86
+ const catIcon = cat.items.every((i) => i.status === "pass") ? ICONS.pass : cat.items.some((i) => i.status === "fail") ? ICONS.fail : ICONS.warn;
87
+ for (const item of cat.items) {
88
+ lines.push(` \u2502 ${ICONS[item.status]} ${chalk.bold(item.name.padEnd(16))} ${item.message}`);
89
+ }
90
+ }
91
+ const hardStatusKey = `report.hard.${report.hardStatus}`;
92
+ const hardWarnCount = report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "warn").length;
93
+ const hardFailCount = report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "fail").length;
94
+ const hardStatusMsg = report.hardStatus === "ready" ? t(hardStatusKey, lang) : t(hardStatusKey, lang, { count: report.hardStatus === "fail" ? hardFailCount : hardWarnCount });
95
+ lines.push(chalk.gray(` \u2502`));
96
+ lines.push(` \u2514\u2500 ${t("report.score", lang)}: ${hardStatusMsg}`);
97
+ lines.push("");
98
+ lines.push(chalk.bold(` \u250C\u2500 ${t("report.soft_scoring", lang)} `) + chalk.gray("\u2500".repeat(Math.max(0, 40 - t("report.soft_scoring", lang).length))) + ` ${scoreColor(report.softScore + "/100")}`);
99
+ for (const cat of report.softCategories) {
100
+ const isAiCat = cat.name.includes("AI") || cat.name.includes("ai");
101
+ const score = isAiCat && report.siteAiScore > 0 ? report.siteAiScore : categoryScore(cat);
102
+ const bar = renderBar(score, 100);
103
+ const pct = `${score}%`;
104
+ lines.push(` \u2502 ${bar} ${pct.padStart(4)} ${cat.name}`);
105
+ }
106
+ if (report.warningPenalty > 0) {
107
+ lines.push(chalk.gray(` \u2502`));
108
+ lines.push(chalk.yellow(` \u2502 \u26A0 ${t("report.warning_ratio", lang, { count: report.warned, total: report.totalChecks, pct: Math.round(report.warningRatio * 100) })} \u2192 ${t("report.warning_penalty", lang, { points: report.warningPenalty })}`));
109
+ }
110
+ lines.push(chalk.gray(` \u2502`));
111
+ const hardContrib = Math.round(report.hardStatus === "ready" ? 100 * 0.4 : report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "pass").length / Math.max(1, report.hardCategories.flatMap((c) => c.items).length) * 100 * 0.4);
112
+ const softContrib = Math.round(report.softScore * 0.6);
113
+ lines.push(chalk.gray(` \u2502 Hard ${Math.round(hardContrib)}% \xD7 0.4 + Soft ${report.softScore}% \xD7 0.6 - Penalty ${report.warningPenalty} = ${report.compositeScore}`));
114
+ if (report.siteAiScore > 0) {
115
+ lines.push(chalk.gray(` \u2502 AI Value Score: ${report.siteAiScore}/100 (geometric mean \xD7 page-type weights)`));
116
+ }
117
+ lines.push(chalk.gray(` \u2514\u2500`));
118
+ lines.push("");
119
+ if (report.categoryScores.length > 0) {
120
+ for (const cs of report.categoryScores) {
121
+ const isAiCat = cs.name.includes("AI") || cs.name.includes("ai");
122
+ const pct = isAiCat && report.siteAiScore > 0 ? report.siteAiScore : cs.maxScore > 0 ? Math.round(cs.score / cs.maxScore * 100) : 0;
123
+ const bar = renderBar(pct, 100);
124
+ lines.push(` ${bar} ${pct}% ${cs.name}`);
125
+ }
126
+ lines.push("");
127
+ }
128
+ for (const cat of report.categories) {
129
+ lines.push(chalk.bold(` ${cat.name}`));
130
+ for (const item of cat.items) {
131
+ lines.push(` ${ICONS[item.status]} [${LABELS[item.status]}] ${item.message}`);
132
+ if (item.detail) lines.push(chalk.gray(` ${item.detail}`));
54
133
  }
55
134
  lines.push("");
56
135
  }
57
136
  if (report.pages.length > 0) {
58
- lines.push(chalk.bold(" Page Details"));
59
- lines.push(chalk.gray(` (${report.pages.length} pages analyzed)`));
137
+ lines.push(chalk.bold(` ${t("report.page_details", lang)}`));
138
+ lines.push(chalk.gray(` (${t("report.pages", lang, { count: report.pages.length })})`));
60
139
  lines.push("");
61
- const problemPages = report.pages.filter(
62
- (p) => p.contentStatus !== "pass" || p.issues.length > 0 || p.ai && p.ai.status !== "pass"
63
- );
64
- const okPages = report.pages.filter(
65
- (p) => p.contentStatus === "pass" && p.issues.length === 0 && (!p.ai || p.ai.status === "pass")
66
- );
67
- for (const page of problemPages) {
68
- renderPageDetail(lines, page, true);
69
- }
70
- if (okPages.length > 0) {
71
- lines.push(chalk.gray(` + ${okPages.length} \u4E2A\u9875\u9762\u65E0\u95EE\u9898`));
72
- lines.push("");
73
- }
140
+ const problems = report.pages.filter((p) => p.contentStatus !== "pass" || p.issues.length > 0 || p.ai && p.ai.status !== "pass");
141
+ const ok = report.pages.filter((p) => p.contentStatus === "pass" && p.issues.length === 0 && (!p.ai || p.ai.status === "pass"));
142
+ for (const p of problems) renderPage(lines, p, lang);
143
+ if (ok.length > 0) lines.push(chalk.gray(` ${t("report.pages_ok", lang, { count: ok.length })}`));
144
+ lines.push("");
145
+ }
146
+ lines.push(chalk.bold(` ${t("report.score", lang)}: `) + `${report.score}/${report.totalChecks}`);
147
+ if (report.hardStatus === "fail") {
148
+ lines.push(chalk.red.bold(` ${t("report.notready", lang, { count: hardFailCount })}`));
149
+ } else if (report.hardStatus === "warn") {
150
+ lines.push(chalk.yellow.bold(` ${t("report.hard.warn", lang, { count: hardWarnCount })}`));
151
+ } else if (report.warned > 0) {
152
+ lines.push(chalk.yellow.bold(` ${t("report.mostly", lang, { count: report.warned })}`));
153
+ } else {
154
+ lines.push(chalk.green.bold(` ${t("report.ready", lang)}`));
155
+ }
156
+ const hasAi = report.categories.some((c) => c.group === "soft" && (c.name.includes("AI") || c.name.includes("ai")));
157
+ if (!hasAi) {
158
+ lines.push("");
159
+ lines.push(chalk.cyan(` \u{1F4A1} ${t("ai.suggest_enable", lang)}`));
74
160
  }
75
- lines.push(chalk.bold(" Score: ") + `${report.score}/${report.totalChecks}`);
76
- lines.push(` Status: ${getStatusSummary(report)}`);
77
161
  lines.push("");
78
162
  return lines.join("\n");
79
163
  }
80
- function renderPageDetail(lines, page, verbose) {
81
- const path = safePath(page.url);
82
- const statusIcon = STATUS_ICONS[page.contentStatus];
164
+ var PAGE_TYPE_ICONS = {
165
+ homepage: chalk.cyan("*"),
166
+ content: chalk.green("A"),
167
+ game_detail: chalk.blue("G"),
168
+ required: chalk.yellow("!"),
169
+ listing: chalk.gray("L"),
170
+ utility: chalk.gray("#"),
171
+ unknown: chalk.gray("?")
172
+ };
173
+ function renderPage(lines, page, lang) {
174
+ const path = (() => {
175
+ try {
176
+ return new URL(page.url).pathname;
177
+ } catch {
178
+ return page.url;
179
+ }
180
+ })();
83
181
  const ratioColor = page.contentRatio >= 50 ? chalk.green : page.contentRatio >= 30 ? chalk.yellow : chalk.red;
84
- lines.push(` ${statusIcon} ${chalk.bold(path)}`);
182
+ const scoreColor = page.score >= 80 ? chalk.green : page.score >= 50 ? chalk.yellow : chalk.red;
183
+ const typeIcon = PAGE_TYPE_ICONS[page.pageType] || chalk.gray("?");
184
+ lines.push(` ${ICONS[page.contentStatus]} ${typeIcon} ${chalk.bold(path)} ${scoreColor(page.score + "/100")}`);
85
185
  lines.push(chalk.gray(` ${page.title}`));
86
- lines.push(` \u6B63\u6587 ${ratioColor(page.contentRatio + "%")} (${page.contentChars}/${page.totalChars} \u5B57)`);
87
- for (const issue of page.issues) {
88
- lines.push(chalk.yellow(` ! ${issue}`));
89
- }
186
+ lines.push(` ${t("report.content_label", lang)} ${ratioColor(page.contentRatio + "%")} (${page.contentChars}/${page.totalChars})`);
187
+ for (const issue of page.issues) lines.push(chalk.yellow(` ! ${issue}`));
90
188
  if (page.ai) {
91
- const aiIcon = STATUS_ICONS[page.ai.status];
92
- lines.push(` ${aiIcon} AI: ${truncate(page.ai.assessment, 80)}`);
93
- for (const s of page.ai.suggestions.slice(0, 2)) {
94
- lines.push(chalk.gray(` \u2192 ${truncate(s, 70)}`));
95
- }
189
+ lines.push(` ${ICONS[page.ai.status]} AI: ${truncate(page.ai.assessment, 80)}`);
190
+ for (const s of page.ai.suggestions.slice(0, 2)) lines.push(chalk.gray(` -> ${truncate(s, 70)}`));
96
191
  }
97
192
  lines.push("");
98
193
  }
99
- function safePath(url) {
100
- try {
101
- return new URL(url).pathname;
102
- } catch {
103
- return url;
104
- }
105
- }
106
194
  function truncate(s, max) {
107
195
  return s.length > max ? s.slice(0, max - 1) + "..." : s;
108
196
  }
@@ -111,11 +199,10 @@ function renderJsonReport(report) {
111
199
  }
112
200
 
113
201
  // src/cli.ts
114
- var __dirname = dirname(fileURLToPath(import.meta.url));
115
202
  function formatTimestamp() {
116
203
  const d = /* @__PURE__ */ new Date();
117
- const pad = (n) => String(n).padStart(2, "0");
118
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
204
+ const p = (n) => String(n).padStart(2, "0");
205
+ return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
119
206
  }
120
207
  function getDomain(url) {
121
208
  try {
@@ -125,53 +212,193 @@ function getDomain(url) {
125
212
  }
126
213
  }
127
214
  var program = new Command();
128
- program.name("adsense-check").description("Check if a website meets Google AdSense review requirements").version("1.0.0").argument("<url>", "Website URL to check").option("-j, --json", "Output as JSON to stdout").option("-d, --depth <number>", "Number of internal pages to crawl", "10").option("-s, --skip-ai", "Skip AI content analysis", false).option("-t, --timeout <ms>", "Page load timeout in milliseconds", "30000").option("--api-key <key>", "AI API key (or set AI_API_KEY in .env)").option("-o, --output <dir>", "Report output directory", "tmp").option("--no-save", "Skip auto-saving report files").action(async (url, opts) => {
215
+ program.name("adsense-check").description("Check if a website meets Google AdSense review requirements").version("1.0.0").argument("<url>", "Website URL to check").option("-j, --json", "Output JSON to stdout").option("-n, --max-crawl <number>", "Total page crawl limit (Phase 1 + 2)", "50").option("-m, --page-limit <number>", "Max structural pages to crawl (Phase 1)", "50").option("-c, --content-limit <number>", "Max content pages to crawl (Phase 2)", "20").option("--sample-min <number>", "Min content pages to sample", "20").option("--sample-ratio <ratio>", "Content page sampling ratio (0-1)", "0.2").option("--ai", "Enable AI content quality analysis", false).option("-t, --timeout <ms>", "Page load timeout", "30000").option("--api-key <key>", "AI API key").option("-o, --output <dir>", "Report output directory", "tmp").option("--no-save", "Skip auto-saving report").option("-l, --lang <lang>", `Output language (${getSupportedLangs().join("|")})`, "en").option("--type <type>", "Force site type (content|tool|game), skip auto-detection").option("--detect-only", "Only detect site type/topic, skip full check").option("--page <url>", "Analyze a single page value (AI four-dimension scoring)").action(async (url, opts) => {
129
216
  try {
130
217
  new URL(url);
131
218
  } catch {
132
219
  console.error(chalk2.red(`Error: Invalid URL "${url}"`));
133
220
  process.exit(1);
134
221
  }
135
- if (!url.startsWith("http")) {
136
- url = "https://" + url;
222
+ if (!url.startsWith("http")) url = "https://" + url;
223
+ const lang = isValidLang(opts.lang) ? opts.lang : "en";
224
+ const validTypes = ["content", "tool", "game"];
225
+ const siteType = validTypes.includes(opts.type) ? opts.type : void 0;
226
+ if (opts.detectOnly) {
227
+ const browser = new BrowserManager();
228
+ try {
229
+ process.stderr.write(chalk2.cyan(`\u25CF Detecting site type for ${url}...
230
+ `));
231
+ const page = await browser.newPage();
232
+ const data = await fetchPage(page, url, parseInt(opts.timeout, 10));
233
+ await page.close();
234
+ const domResult = detectSiteType([data.signals], data.navText + " " + data.footerText, siteType);
235
+ process.stderr.write(chalk2.gray(` DOM detection: ${domResult.type} (${domResult.confidence})
236
+ `));
237
+ const apiKey = opts.apiKey || process.env.AI_API_KEY;
238
+ if (opts.ai && apiKey) {
239
+ process.stderr.write(chalk2.gray(" AI: analyzing topic...\n"));
240
+ const topic = await analyzeSiteTopic(
241
+ { title: data.title, text: data.text, navText: data.navText + " " + data.footerText },
242
+ lang,
243
+ apiKey
244
+ );
245
+ console.log(JSON.stringify({
246
+ domType: domResult.type,
247
+ domConfidence: domResult.confidence,
248
+ aiType: topic.type,
249
+ topic: topic.topic,
250
+ description: topic.description,
251
+ confidence: topic.confidence,
252
+ reasoning: topic.reasoning
253
+ }, null, 2));
254
+ } else {
255
+ console.log(JSON.stringify({
256
+ type: domResult.type,
257
+ confidence: domResult.confidence,
258
+ signals: domResult.signals
259
+ }, null, 2));
260
+ }
261
+ await browser.close();
262
+ process.exit(0);
263
+ } catch (err) {
264
+ await browser.close();
265
+ console.error(chalk2.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
266
+ process.exit(2);
267
+ }
137
268
  }
138
- const spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
139
- let frame = 0;
140
- const interval = setInterval(() => {
141
- process.stderr.write(`\r${chalk2.cyan(spinner[frame++ % spinner.length])} Checking ${url}...`);
142
- }, 80);
269
+ if (opts.page) {
270
+ const pageUrl = opts.page.startsWith("http") ? opts.page : "https://" + opts.page;
271
+ const apiKey = opts.apiKey || process.env.AI_API_KEY;
272
+ if (!apiKey) {
273
+ console.error(chalk2.red("Error: --page requires AI (--ai --api-key or AI_API_KEY env)"));
274
+ process.exit(1);
275
+ }
276
+ const browser = new BrowserManager();
277
+ try {
278
+ process.stderr.write(chalk2.cyan(`\u25CF Analyzing page value: ${pageUrl}
279
+ `));
280
+ const pg = await browser.newPage();
281
+ const data = await fetchPage(pg, pageUrl, parseInt(opts.timeout, 10));
282
+ await pg.close();
283
+ process.stderr.write(chalk2.gray(" Detecting topic...\n"));
284
+ let siteTopic;
285
+ try {
286
+ siteTopic = await analyzeSiteTopic(
287
+ { title: data.title, text: data.text, navText: data.navText + " " + data.footerText },
288
+ lang,
289
+ apiKey
290
+ );
291
+ process.stderr.write(chalk2.gray(` Topic: ${siteTopic.topic} (${siteTopic.type})
292
+ `));
293
+ } catch {
294
+ }
295
+ process.stderr.write(chalk2.gray(" AI: analyzing page value...\n"));
296
+ const result = await analyzeWithAI(
297
+ [{ url: pageUrl, text: data.text }],
298
+ lang,
299
+ apiKey,
300
+ void 0,
301
+ siteTopic
302
+ );
303
+ const analysis = result.pageAnalyses[0];
304
+ if (!analysis) {
305
+ console.error(chalk2.red(" AI analysis returned no results"));
306
+ process.exit(2);
307
+ }
308
+ const score = computePageAiScore(analysis);
309
+ const v = analysis.valueScore ?? 5;
310
+ const o = analysis.originalityScore ?? 5;
311
+ const r = analysis.relevanceScore ?? 5;
312
+ const c = analysis.complianceScore ?? 5;
313
+ const dimColor = (s) => s >= 8 ? chalk2.green : s >= 5 ? chalk2.yellow : chalk2.red;
314
+ const scoreColor = score >= 70 ? chalk2.green : score >= 40 ? chalk2.yellow : chalk2.red;
315
+ const lines = [
316
+ "",
317
+ chalk2.bold.cyan(" Page Value Analysis"),
318
+ chalk2.gray(` URL: ${pageUrl}`),
319
+ chalk2.gray(` Title: ${data.title}`)
320
+ ];
321
+ if (siteTopic) {
322
+ lines.push(chalk2.gray(` Site topic: ${siteTopic.topic} (${siteTopic.type})`));
323
+ }
324
+ lines.push("");
325
+ lines.push(` ${dimColor(v)("Value")} ${v}/10`);
326
+ lines.push(` ${dimColor(o)("Originality")} ${o}/10`);
327
+ lines.push(` ${dimColor(r)("Relevance")} ${r}/10`);
328
+ lines.push(` ${dimColor(c)("Compliance")} ${c}/10`);
329
+ lines.push("");
330
+ lines.push(chalk2.bold(` Overall: ${scoreColor(score + "/100")}`) + chalk2.gray(" (geometric mean)"));
331
+ lines.push("");
332
+ if (analysis.assessment) {
333
+ lines.push(chalk2.gray(` ${analysis.assessment}`));
334
+ lines.push("");
335
+ }
336
+ for (const s of analysis.suggestions) {
337
+ lines.push(chalk2.yellow(` -> ${s}`));
338
+ }
339
+ lines.push("");
340
+ if (opts.json) {
341
+ console.log(JSON.stringify({
342
+ url: pageUrl,
343
+ title: data.title,
344
+ topic: siteTopic,
345
+ scores: { value: v, originality: o, relevance: r, compliance: c },
346
+ overall: score,
347
+ relevanceLabel: analysis.relevance,
348
+ assessment: analysis.assessment,
349
+ suggestions: analysis.suggestions
350
+ }, null, 2));
351
+ } else {
352
+ console.log(lines.join("\n"));
353
+ }
354
+ await browser.close();
355
+ process.exit(c <= 2 ? 1 : 0);
356
+ } catch (err) {
357
+ await browser.close();
358
+ console.error(chalk2.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
359
+ process.exit(2);
360
+ }
361
+ }
362
+ process.stderr.write(chalk2.cyan(`\u25CF Checking ${url}...
363
+ `));
143
364
  try {
365
+ let lastProgress = "";
144
366
  const report = await check({
145
367
  url,
146
- depth: parseInt(opts.depth, 10),
147
- skipAi: opts.skipAi,
368
+ maxCrawl: parseInt(opts.maxCrawl, 10),
369
+ maxPages: parseInt(opts.pageLimit, 10),
370
+ maxContent: parseInt(opts.contentLimit, 10),
371
+ sampleMin: parseInt(opts.sampleMin, 10),
372
+ sampleRatio: parseFloat(opts.sampleRatio),
373
+ siteType,
374
+ skipAi: !opts.ai,
148
375
  timeout: parseInt(opts.timeout, 10),
149
- apiKey: opts.apiKey
376
+ apiKey: opts.apiKey,
377
+ lang,
378
+ onProgress: (msg) => {
379
+ lastProgress = msg;
380
+ const line = `\r${chalk2.cyan("\u25CF")} ${chalk2.gray(msg)}`;
381
+ process.stderr.write(line + " ".repeat(Math.max(0, 60 - msg.length)));
382
+ }
150
383
  });
151
- clearInterval(interval);
152
- process.stderr.write("\r" + " ".repeat(60) + "\r");
153
- if (opts.json) {
154
- console.log(renderJsonReport(report));
155
- } else {
156
- console.log(renderTerminalReport(report));
157
- }
384
+ process.stderr.write("\r" + " ".repeat(80) + "\r");
385
+ if (opts.json) console.log(renderJsonReport(report));
386
+ else console.log(renderTerminalReport(report));
158
387
  if (opts.save !== false) {
159
388
  const ts = formatTimestamp();
160
389
  const domain = getDomain(url);
161
390
  const outDir = join(process.cwd(), opts.output);
162
391
  try {
163
392
  mkdirSync(outDir, { recursive: true });
164
- const jsonPath = join(outDir, `${domain}-${ts}.json`);
165
- writeFileSync(jsonPath, renderJsonReport(report), "utf-8");
166
- console.log(chalk2.gray(` Report saved: ${jsonPath}`));
167
- } catch (saveErr) {
168
- console.error(chalk2.yellow(` Warning: Failed to save report: ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`));
393
+ const path = join(outDir, `${domain}-${ts}.json`);
394
+ writeFileSync(path, renderJsonReport(report), "utf-8");
395
+ console.log(chalk2.gray(` ${t("report.saved", lang)}: ${path}`));
396
+ } catch {
169
397
  }
170
398
  }
171
399
  process.exit(report.failed > 0 ? 1 : 0);
172
400
  } catch (err) {
173
- clearInterval(interval);
174
- process.stderr.write("\r" + " ".repeat(60) + "\r");
401
+ process.stderr.write("\r" + " ".repeat(80) + "\r");
175
402
  console.error(chalk2.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
176
403
  process.exit(2);
177
404
  }
package/dist/index.d.ts CHANGED
@@ -1,32 +1,65 @@
1
1
  type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip';
2
+ type Lang = string;
3
+ type SiteType = 'content' | 'tool' | 'game' | 'unsupported';
4
+ type PageType = 'homepage' | 'content' | 'game_detail' | 'required' | 'listing' | 'utility' | 'unknown';
2
5
  interface CheckItem {
3
6
  name: string;
4
7
  status: CheckStatus;
5
8
  message: string;
6
9
  detail?: string;
7
10
  }
11
+ type CheckGroup = 'hard' | 'soft';
8
12
  interface CheckCategory {
9
13
  name: string;
10
14
  items: CheckItem[];
15
+ group?: CheckGroup;
11
16
  }
12
17
  interface PageDetail {
13
18
  url: string;
14
19
  title: string;
20
+ pageType: PageType;
15
21
  totalChars: number;
16
22
  contentChars: number;
17
23
  contentRatio: number;
18
24
  contentStatus: CheckStatus;
19
25
  issues: string[];
26
+ score: number;
27
+ relevance?: 'relevant' | 'tangential' | 'off-topic';
20
28
  ai?: {
21
29
  status: CheckStatus;
22
30
  assessment: string;
23
31
  suggestions: string[];
24
32
  };
25
33
  }
34
+ interface SiteTopic {
35
+ type: SiteType;
36
+ topic: string;
37
+ description: string;
38
+ confidence: 'high' | 'medium' | 'low';
39
+ reasoning: string;
40
+ }
41
+ interface CategoryScore {
42
+ name: string;
43
+ score: number;
44
+ maxScore: number;
45
+ }
26
46
  interface CheckReport {
27
47
  url: string;
28
48
  timestamp: string;
49
+ lang: Lang;
50
+ siteType: SiteType;
51
+ siteTypeConfidence: 'high' | 'medium' | 'low';
52
+ siteTopic?: SiteTopic;
53
+ samplingInfo?: {
54
+ totalDiscovered: number;
55
+ recentCount: number;
56
+ sampledCount: number;
57
+ samplePct: number;
58
+ confidence: 'high' | 'medium' | 'low';
59
+ };
29
60
  categories: CheckCategory[];
61
+ hardCategories: CheckCategory[];
62
+ softCategories: CheckCategory[];
30
63
  score: number;
31
64
  totalChecks: number;
32
65
  passed: number;
@@ -34,13 +67,27 @@ interface CheckReport {
34
67
  failed: number;
35
68
  skipped: number;
36
69
  pages: PageDetail[];
70
+ compositeScore: number;
71
+ categoryScores: CategoryScore[];
72
+ hardStatus: 'ready' | 'warn' | 'fail';
73
+ softScore: number;
74
+ warningRatio: number;
75
+ warningPenalty: number;
76
+ siteAiScore: number;
37
77
  }
38
78
  interface CheckOptions {
39
79
  url: string;
40
- depth?: number;
80
+ maxCrawl?: number;
81
+ maxPages?: number;
82
+ maxContent?: number;
83
+ sampleMin?: number;
84
+ sampleRatio?: number;
85
+ siteType?: SiteType;
41
86
  skipAi?: boolean;
42
87
  timeout?: number;
43
88
  apiKey?: string;
89
+ lang?: Lang;
90
+ onProgress?: (message: string) => void;
44
91
  }
45
92
 
46
93
  declare function check(options: CheckOptions): Promise<CheckReport>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  check
3
- } from "./chunk-V2YZ36NU.js";
3
+ } from "./chunk-SES7MRFF.js";
4
4
  export {
5
5
  check
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudcreate/adsense-check",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Check if a website meets Google AdSense review requirements",
5
5
  "homepage": "https://cloudcreate.ai",
6
6
  "repository": {