@queryrev/ai-readiness 0.0.0-stage → 1.0.1
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/CITATION.cff +31 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/SECURITY.md +34 -0
- package/data/LICENSE +409 -0
- package/dist/chunks/cli-BK65ic84.js +56 -0
- package/dist/chunks/fetch-text-NUUnM91O.js +866 -0
- package/dist/chunks/findings-CTetT2bF.js +2950 -0
- package/dist/chunks/main-Bd0uzoHe.js +1491 -0
- package/dist/cli/args.d.ts +27 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/commands/check.d.ts +4 -0
- package/dist/cli/commands/crawlers.d.ts +3 -0
- package/dist/cli/commands/llms-generate.d.ts +4 -0
- package/dist/cli/commands/llms-validate.d.ts +4 -0
- package/dist/cli/commands/robots-test.d.ts +4 -0
- package/dist/cli/common.d.ts +27 -0
- package/dist/cli/finding.d.ts +3 -0
- package/dist/cli/main.d.ts +7 -0
- package/dist/cli/node-context.d.ts +3 -0
- package/dist/cli/render.d.ts +26 -0
- package/dist/cli/run.d.ts +11 -0
- package/dist/cli/sanitize.d.ts +8 -0
- package/dist/cli/types.d.ts +59 -0
- package/dist/cli/usage.d.ts +1 -0
- package/dist/cli.js +36 -0
- package/dist/crawlers/index.d.ts +79 -0
- package/dist/facts/app-shell.d.ts +24 -0
- package/dist/facts/check-site.d.ts +13 -0
- package/dist/facts/domain.d.ts +15 -0
- package/dist/facts/findings.d.ts +5 -0
- package/dist/facts/home-page.d.ts +18 -0
- package/dist/facts/home-response.d.ts +5 -0
- package/dist/facts/html.d.ts +37 -0
- package/dist/facts/index.d.ts +16 -0
- package/dist/facts/json-ld.d.ts +35 -0
- package/dist/facts/marks.d.ts +51 -0
- package/dist/facts/organization.d.ts +23 -0
- package/dist/facts/origin-robots.d.ts +20 -0
- package/dist/facts/profiles.d.ts +13 -0
- package/dist/facts/reader.d.ts +2 -0
- package/dist/facts/schema-org-organization-types.d.ts +6 -0
- package/dist/facts/schema-org-types.d.ts +2 -0
- package/dist/facts/sitemap.d.ts +24 -0
- package/dist/facts/types.d.ts +214 -0
- package/dist/facts/words.d.ts +15 -0
- package/dist/generate/generate-llms.d.ts +51 -0
- package/dist/generate/index.d.ts +70 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1023 -0
- package/dist/llms/findings.d.ts +7 -0
- package/dist/llms/index.d.ts +88 -0
- package/dist/llms/links.d.ts +46 -0
- package/dist/net/address.d.ts +26 -0
- package/dist/net/charset.d.ts +20 -0
- package/dist/net/cli.d.ts +13 -0
- package/dist/net/fetch-text.d.ts +191 -0
- package/dist/net/guard.d.ts +71 -0
- package/dist/net/testing/fixture-fetcher.d.ts +15 -0
- package/dist/net/testing/fixture-guard.d.ts +20 -0
- package/dist/net/testing/index.d.ts +7 -0
- package/dist/net-cli.js +1 -0
- package/dist/robots/index.d.ts +67 -0
- package/dist/run.js +1 -0
- package/dist/testing.js +58 -0
- package/package.json +78 -4
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type OptionKind = 'boolean' | 'string';
|
|
2
|
+
interface OptionSpec {
|
|
3
|
+
readonly type: OptionKind;
|
|
4
|
+
readonly short?: string;
|
|
5
|
+
}
|
|
6
|
+
export type CommandName = 'check' | 'robots test' | 'llms validate' | 'llms generate' | 'crawlers';
|
|
7
|
+
interface CommandSpec {
|
|
8
|
+
readonly name: CommandName;
|
|
9
|
+
readonly options: Readonly<Record<string, OptionSpec>>;
|
|
10
|
+
readonly positionals: {
|
|
11
|
+
readonly min: number;
|
|
12
|
+
readonly max: number;
|
|
13
|
+
readonly label: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare const COMMANDS: Readonly<Record<CommandName, CommandSpec>>;
|
|
17
|
+
export interface ParsedCommand {
|
|
18
|
+
readonly command: CommandName;
|
|
19
|
+
readonly positionals: readonly string[];
|
|
20
|
+
readonly values: Readonly<Record<string, string | boolean | undefined>>;
|
|
21
|
+
}
|
|
22
|
+
export declare function parseCommand(argv: readonly string[], offersPrivateNetwork?: boolean): ParsedCommand;
|
|
23
|
+
export type FailOn = 'fail' | 'warn';
|
|
24
|
+
export declare function failOnOf(parsed: ParsedCommand): FailOn;
|
|
25
|
+
export declare const flag: (parsed: ParsedCommand, name: string) => boolean;
|
|
26
|
+
export declare const text: (parsed: ParsedCommand, name: string) => string | undefined;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ParsedCommand } from '../args.js';
|
|
2
|
+
import { makeStyle } from '../render.js';
|
|
3
|
+
import { type CliContext, type CommandResult } from '../types.js';
|
|
4
|
+
export declare function runCheck(context: CliContext, parsed: ParsedCommand, style: ReturnType<typeof makeStyle>): Promise<CommandResult>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ParsedCommand } from '../args.js';
|
|
2
|
+
import { type Style } from '../render.js';
|
|
3
|
+
import { type CliContext, type CommandResult } from '../types.js';
|
|
4
|
+
export declare function runLlmsGenerate(context: CliContext, parsed: ParsedCommand, style: Style): Promise<CommandResult>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ParsedCommand } from '../args.js';
|
|
2
|
+
import { type Style } from '../render.js';
|
|
3
|
+
import { type CliContext, type CommandResult } from '../types.js';
|
|
4
|
+
export declare function runLlmsValidate(context: CliContext, parsed: ParsedCommand, style: Style): Promise<CommandResult>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ParsedCommand } from '../args.js';
|
|
2
|
+
import { type Style } from '../render.js';
|
|
3
|
+
import { type CliContext, type CommandResult } from '../types.js';
|
|
4
|
+
export declare function runRobotsTest(context: CliContext, parsed: ParsedCommand, style: Style): Promise<CommandResult>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type TextFetchResult } from '../net/fetch-text.js';
|
|
2
|
+
import type { Finding } from '../facts/types.js';
|
|
3
|
+
import type { FailOn } from './args.js';
|
|
4
|
+
import { type CliContext, type ExitCode } from './types.js';
|
|
5
|
+
/** 1 when a finding is at or above the threshold, else 0. */
|
|
6
|
+
export declare function exitForFindings(findings: readonly Finding[], failOn: FailOn): ExitCode;
|
|
7
|
+
/** Why a fetch gave no usable answer, in a few plain words. Empty for an answer. */
|
|
8
|
+
export declare function describeFetchFailure(result: TextFetchResult, offersPrivateNetwork?: boolean): string;
|
|
9
|
+
export interface Source {
|
|
10
|
+
readonly kind: 'file' | 'url';
|
|
11
|
+
/** The path, or the address. */
|
|
12
|
+
readonly location: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Tells a file from a site: an http(s) address is a site, a name that is an existing file is a
|
|
16
|
+
* file, and a bare host name (`example.com`) is a site.
|
|
17
|
+
*/
|
|
18
|
+
export declare function classifySource(context: CliContext, input: string, maxBytes: number): Promise<{
|
|
19
|
+
source: Source;
|
|
20
|
+
file: Awaited<ReturnType<CliContext['readFile']>> | null;
|
|
21
|
+
}>;
|
|
22
|
+
/** The address of a well-known file on a site: the given address, or `/name` at the site's root. */
|
|
23
|
+
export declare function fileAddress(input: string, name: string): string;
|
|
24
|
+
export declare const ROBOTS_READ_LIMIT: number;
|
|
25
|
+
export declare const LLMS_READ_LIMIT: number;
|
|
26
|
+
/** The text of a file read from disk; a byte order mark is left for the parsers to handle. */
|
|
27
|
+
export declare function decodeFile(bytes: Uint8Array): string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Finding, FindingLevel } from '../facts/types.js';
|
|
2
|
+
/** A finding of the command line's own checks (a robots.txt test); the evidence is cut short. */
|
|
3
|
+
export declare function cliFinding(id: string, level: FindingLevel, message: string, snippet?: string, anchor?: string): Finding;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type CliContext, type ExitCode } from './types.js';
|
|
2
|
+
export declare const CLI_VERSION: string;
|
|
3
|
+
/**
|
|
4
|
+
* Runs the command line. Returns the exit code and sets no global state: output goes through the
|
|
5
|
+
* context, so a test can run it with a fixture fetcher and read what it printed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runCli(argv: readonly string[], context: CliContext): Promise<ExitCode>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { crawlersCoveredLine } from '../crawlers/index.js';
|
|
2
|
+
import type { Finding, FindingLevel } from '../facts/types.js';
|
|
3
|
+
/** The "full report" link every human report ends with. It is printed, never opened. */
|
|
4
|
+
export declare function fullReportUrl(command: string): string;
|
|
5
|
+
/** What this tool cannot see, said once in the same words wherever it matters. */
|
|
6
|
+
export declare const CANNOT_SEE = "Not seen: a CDN or firewall rule can answer AI crawlers differently from how it answers this tool.";
|
|
7
|
+
export interface Style {
|
|
8
|
+
bold(text: string): string;
|
|
9
|
+
dim(text: string): string;
|
|
10
|
+
level(level: FindingLevel, text: string): string;
|
|
11
|
+
}
|
|
12
|
+
/** Colour only on a terminal, and never when NO_COLOR is set or `--no-color` is given. */
|
|
13
|
+
export declare function makeStyle(options: {
|
|
14
|
+
readonly isTTY: boolean;
|
|
15
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
16
|
+
readonly noColorFlag: boolean;
|
|
17
|
+
}): Style;
|
|
18
|
+
export declare function summarize(findings: readonly Finding[]): Record<FindingLevel, number>;
|
|
19
|
+
/** The findings as lines: a label, the id, the sentence, and the evidence under it. */
|
|
20
|
+
export declare function renderFindings(findings: readonly Finding[], style: Style): string;
|
|
21
|
+
export declare function renderSummary(findings: readonly Finding[]): string;
|
|
22
|
+
/** The covered crawlers as a table: token, vendor, purpose. */
|
|
23
|
+
export declare function renderCrawlerList(style: Style): string;
|
|
24
|
+
export { crawlersCoveredLine };
|
|
25
|
+
/** A table with a header row; cells are padded to the widest entry of their column. */
|
|
26
|
+
export declare function renderTable(header: readonly string[], rows: readonly (readonly string[])[], style: Style): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command runner (`@queryrev/ai-readiness/run`): the same code the command line runs, with
|
|
3
|
+
* the surroundings (streams, files, the fetcher) passed in. A program that offers the commands
|
|
4
|
+
* to another program, such as an MCP server, runs them here and prints exactly what
|
|
5
|
+
* `ai-readiness <command> --json` prints.
|
|
6
|
+
*
|
|
7
|
+
* It does not include the command line's real file system or its private-network fetcher: the
|
|
8
|
+
* caller builds the context.
|
|
9
|
+
*/
|
|
10
|
+
export { runCli, CLI_VERSION } from './main.js';
|
|
11
|
+
export type { CliContext, ExitCode, FileRead, Writable } from './types.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text that came from a site (a title, a heading, a line of its robots.txt) can carry terminal
|
|
3
|
+
* escape sequences and characters that reorder how text is shown. Every string of a result goes
|
|
4
|
+
* through here before it is printed or written as JSON.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sanitizeDeep<T>(value: T): T;
|
|
7
|
+
/** One line: whitespace runs, line breaks included, become one space. */
|
|
8
|
+
export declare function oneLine(text: string): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { TextFetcher } from '../net/fetch-text.js';
|
|
2
|
+
/**
|
|
3
|
+
* Exit codes, the contract of the command line:
|
|
4
|
+
*
|
|
5
|
+
* - 0: nothing at or above `--fail-on` (`fail` unless asked otherwise)
|
|
6
|
+
* - 1: the checks failed
|
|
7
|
+
* - 2: usage error (a wrong command, option or input file)
|
|
8
|
+
* - 3: the site could not be read (refused, unreachable, timed out, or robots.txt unreadable)
|
|
9
|
+
* - 4: internal error
|
|
10
|
+
*/
|
|
11
|
+
export type ExitCode = 0 | 1 | 2 | 3 | 4;
|
|
12
|
+
export declare const EXIT_OK = 0;
|
|
13
|
+
export declare const EXIT_CHECKS_FAILED = 1;
|
|
14
|
+
export declare const EXIT_USAGE = 2;
|
|
15
|
+
export declare const EXIT_UNREADABLE = 3;
|
|
16
|
+
export declare const EXIT_INTERNAL = 4;
|
|
17
|
+
export interface Writable {
|
|
18
|
+
write(chunk: string): unknown;
|
|
19
|
+
readonly isTTY?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface FileRead {
|
|
22
|
+
readonly bytes: Uint8Array;
|
|
23
|
+
/** The file was longer than the limit and was cut there. */
|
|
24
|
+
readonly truncated: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Everything the command line touches outside its own code, so a test can replace it. */
|
|
27
|
+
export interface CliContext {
|
|
28
|
+
readonly stdout: Writable;
|
|
29
|
+
readonly stderr: Writable;
|
|
30
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
31
|
+
/** Reads at most `maxBytes` of a file; throws an error with a `code` (`ENOENT`) when it cannot. */
|
|
32
|
+
readonly readFile: (path: string, maxBytes: number) => Promise<FileRead>;
|
|
33
|
+
/** Writes a file; without `overwrite` it fails when the file exists. */
|
|
34
|
+
readonly writeFile: (path: string, text: string, overwrite: boolean) => Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Whether the commands offer `--allow-private-network` (default true). A program that embeds
|
|
37
|
+
* the commands and has no such option sets this to false: the flag is then an unknown option
|
|
38
|
+
* and no message mentions it.
|
|
39
|
+
*/
|
|
40
|
+
readonly offersPrivateNetwork?: boolean;
|
|
41
|
+
/** One fetcher per run; the command line closes it. */
|
|
42
|
+
readonly createFetcher: (options: {
|
|
43
|
+
allowPrivateNetwork: boolean;
|
|
44
|
+
}) => TextFetcher;
|
|
45
|
+
}
|
|
46
|
+
/** What a command returns; `runCli` prints it. */
|
|
47
|
+
export interface CommandResult {
|
|
48
|
+
readonly exit: ExitCode;
|
|
49
|
+
/** The `--json` document. */
|
|
50
|
+
readonly json: unknown;
|
|
51
|
+
/** The human report, for standard output. */
|
|
52
|
+
readonly text: string;
|
|
53
|
+
/** Notes for standard error in the human mode (a generated file goes to standard output). */
|
|
54
|
+
readonly textErr?: string;
|
|
55
|
+
}
|
|
56
|
+
/** A wrong command, option or input: exit code 2. */
|
|
57
|
+
export declare class UsageError extends Error {
|
|
58
|
+
readonly name = "UsageError";
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const USAGE = "ai-readiness: checks how a site's public files treat AI crawlers.\n\nUsage: ai-readiness <command> [arguments] [options]\n\nCommands\n check <url> robots.txt access of the covered AI crawlers, llms.txt, the\n home page as served, structured data and the sitemap\n robots test <file|url> which robots.txt line decides for a crawler and a path\n --agent <token> one crawler token (any token); all covered crawlers without it\n --path <path> the path to test, default /\n llms validate <file|url> checks an llms.txt against llmstxt.org\n --links also request up to 20 links on the same site\n --base <url> the site of a local file's links (needed with --links)\n llms generate <url> builds the skeleton of an llms.txt from the site's own pages\n --max-pages <n> pages to list, 1 to 100, default 30\n --out <file> write the file here (--force replaces an existing file)\n crawlers the AI crawlers covered, with vendor and purpose\n\nOptions\n --json one JSON document with schemaVersion 1, no colour, no other output\n --fail-on <level> exit 1 for findings at \"fail\" (default) or at \"warn\" (check, robots test, llms validate)\n --allow-private-network\n reach a staging site on a private network (never cloud metadata addresses)\n --no-color no colour (also off without a terminal, or when NO_COLOR is set)\n -h, --help this text\n -v, --version the version\n\nExit codes\n 0 nothing at or above --fail-on\n 1 checks failed\n 2 usage error\n 3 the site could not be read (refused, unreachable, timed out)\n 4 internal error\n\nReads public files only. Sends nothing to queryrev.com.\n";
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as runCli } from "./chunks/main-Bd0uzoHe.js";
|
|
3
|
+
import { n as createCliTextFetcher } from "./chunks/cli-BK65ic84.js";
|
|
4
|
+
import { argv, env, stderr, stdout } from "node:process";
|
|
5
|
+
import { open, writeFile } from "node:fs/promises";
|
|
6
|
+
// src/cli/node-context.ts
|
|
7
|
+
async function readFile(path, maxBytes) {
|
|
8
|
+
const handle = await open(path, "r");
|
|
9
|
+
try {
|
|
10
|
+
const buffer = Buffer.alloc(maxBytes + 1);
|
|
11
|
+
const { bytesRead } = await handle.read(buffer, 0, maxBytes + 1, 0);
|
|
12
|
+
return {
|
|
13
|
+
bytes: buffer.subarray(0, Math.min(bytesRead, maxBytes)),
|
|
14
|
+
truncated: bytesRead > maxBytes
|
|
15
|
+
};
|
|
16
|
+
} finally {
|
|
17
|
+
await handle.close();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** The command line's real surroundings: this process's streams, environment and file system. */
|
|
21
|
+
function nodeContext() {
|
|
22
|
+
return {
|
|
23
|
+
stdout,
|
|
24
|
+
stderr,
|
|
25
|
+
env,
|
|
26
|
+
readFile,
|
|
27
|
+
writeFile: (path, text, overwrite) => writeFile(path, text, {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
flag: overwrite ? "w" : "wx"
|
|
30
|
+
}),
|
|
31
|
+
createFetcher: ({ allowPrivateNetwork }) => createCliTextFetcher({ allowPrivateNetwork })
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// src/cli/bin.ts
|
|
35
|
+
process.exitCode = await runCli(argv.slice(2), nodeContext());
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type RobotsTxt, type RuleMatch } from '../robots/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Which AI crawlers are measured, and what a robots.txt says to each of them. Nothing outside
|
|
4
|
+
* `STUDY_AGENTS` is measured or named.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The product tokens measured, and the catch-all. The study names the vendors it measures on its
|
|
8
|
+
* page; any crawler not listed here is neither measured nor named.
|
|
9
|
+
*/
|
|
10
|
+
export declare const STUDY_AGENTS: readonly ["GPTBot", "OAI-SearchBot", "ChatGPT-User", "PerplexityBot", "Perplexity-User", "Google-Extended", "Applebot-Extended", "CCBot", "Meta-ExternalAgent", "Amazonbot", "Bytespider", "*"];
|
|
11
|
+
export type StudyAgent = (typeof STUDY_AGENTS)[number];
|
|
12
|
+
export type AiAgent = Exclude<StudyAgent, '*'>;
|
|
13
|
+
/** The AI agents (every measured token but the catch-all). */
|
|
14
|
+
export declare const AI_AGENTS: AiAgent[];
|
|
15
|
+
/**
|
|
16
|
+
* What the rules of a robots.txt mean for one agent, read with the group that applies to it
|
|
17
|
+
* (its own named group, otherwise the `*` group; RFC 9309 2.2.1):
|
|
18
|
+
*
|
|
19
|
+
* - `blocked`: the whole site is closed to it: a rule for the whole site (`/`, `/*`, `*`)
|
|
20
|
+
* disallows `/` and no narrower `Allow` rule opens any other path.
|
|
21
|
+
* - `partial`: some of the site is closed to it: `/` is disallowed but an `Allow` rule opens some
|
|
22
|
+
* paths, or `/` is disallowed only by a narrower rule (`Disallow: /$` closes the home page,
|
|
23
|
+
* not the site); or `/` is open but the group that applies also disallows the whole site (an
|
|
24
|
+
* `Allow: /$` exception to `Disallow: /`); or the agent has a group of its own that disallows
|
|
25
|
+
* a path the `*` group does not. (Paths a site keeps from every crawler, under `*`, are
|
|
26
|
+
* housekeeping: an agent that inherits them, or whose own group repeats them, is `allowed`.)
|
|
27
|
+
* - `allowed`: anything else, a missing robots.txt included.
|
|
28
|
+
*
|
|
29
|
+
* `named` tells a rule written for the agent from one it inherits from `*`.
|
|
30
|
+
*/
|
|
31
|
+
export type AgentAccess = 'allowed' | 'partial' | 'blocked';
|
|
32
|
+
export interface AgentVerdict {
|
|
33
|
+
readonly access: AgentAccess;
|
|
34
|
+
/** The robots.txt has a group naming this agent's product token (never true for `*`). */
|
|
35
|
+
readonly named: boolean;
|
|
36
|
+
/** Whether the rules that apply to the agent let it fetch `/llms.txt`. */
|
|
37
|
+
readonly llmsTxt: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare function agentVerdict(robots: RobotsTxt, agent: string): AgentVerdict;
|
|
40
|
+
/** The verdicts of every measured agent for one robots.txt. */
|
|
41
|
+
export declare function verdictsFor(robots: RobotsTxt): Record<StudyAgent, AgentVerdict>;
|
|
42
|
+
/** What a crawler is for: collecting pages for AI training, indexing for AI search, or fetching a page when a user asks. */
|
|
43
|
+
export type CrawlerPurpose = 'training' | 'search' | 'user';
|
|
44
|
+
/** The purposes, in the order they are listed. */
|
|
45
|
+
export declare const CRAWLER_PURPOSES: readonly CrawlerPurpose[];
|
|
46
|
+
export interface CrawlerInfo {
|
|
47
|
+
/** The product token of the crawler, as it is written in a `User-agent` line. */
|
|
48
|
+
readonly token: AiAgent;
|
|
49
|
+
readonly vendor: string;
|
|
50
|
+
readonly purpose: CrawlerPurpose;
|
|
51
|
+
}
|
|
52
|
+
/** The crawlers covered, in the order of `AI_AGENTS`. No other crawler is measured or named. */
|
|
53
|
+
export declare const CRAWLERS: readonly CrawlerInfo[];
|
|
54
|
+
/**
|
|
55
|
+
* The scope line every report carries: which crawlers were checked. A report never claims
|
|
56
|
+
* to cover every AI crawler.
|
|
57
|
+
*/
|
|
58
|
+
export declare function crawlersCoveredLine(): string;
|
|
59
|
+
/** What a robots.txt says to one covered crawler, for the site and for one path. */
|
|
60
|
+
export interface CrawlerAccess {
|
|
61
|
+
readonly crawler: CrawlerInfo;
|
|
62
|
+
/** The verdict for the whole site (see `agentVerdict`). */
|
|
63
|
+
readonly access: AgentAccess;
|
|
64
|
+
readonly named: boolean;
|
|
65
|
+
/** The rule that decides `/`. */
|
|
66
|
+
readonly root: RuleMatch;
|
|
67
|
+
/** The rule that decides the path asked about; null when no path other than `/` was asked. */
|
|
68
|
+
readonly path: RuleMatch | null;
|
|
69
|
+
/** Whether the crawler may fetch `/llms.txt`. */
|
|
70
|
+
readonly llmsTxt: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The access of every covered crawler: the verdict for the site, and the deciding rule for `/`
|
|
74
|
+
* and for `path` (a path and query, such as `/blog/post?x=1`). A missing robots.txt is an
|
|
75
|
+
* empty one: pass `parseRobots('')`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function crawlerAccess(robots: RobotsTxt, options?: {
|
|
78
|
+
path?: string;
|
|
79
|
+
}): CrawlerAccess[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The empty application shell: an HTML document over a few KiB that carries almost no text and
|
|
3
|
+
* an application root element, or asks the reader to enable JavaScript. The constants are the
|
|
4
|
+
* ones the site crawler's render decision uses (it imports them from here), so a page is called
|
|
5
|
+
* a shell by one rule everywhere.
|
|
6
|
+
*/
|
|
7
|
+
/** An empty shell is HTML over this many bytes (5 KB, read as 5 KiB)... */
|
|
8
|
+
export declare const EMPTY_SHELL_MIN_BYTES: number;
|
|
9
|
+
/** ...with fewer than this many words of main text... */
|
|
10
|
+
export declare const EMPTY_SHELL_MAX_WORDS = 50;
|
|
11
|
+
/**
|
|
12
|
+
* ...and an application root element. Ids that the common frameworks mount into: React
|
|
13
|
+
* (create-react-app, Vite), Vue, Next.js, Nuxt, Gatsby, SvelteKit, Quasar, Remix, Docusaurus.
|
|
14
|
+
*/
|
|
15
|
+
export declare const APP_ROOT_IDS: readonly string[];
|
|
16
|
+
/** Elements and attributes that mark an application root besides the ids (Angular, AngularJS, React, Vue SSR). */
|
|
17
|
+
export declare const APP_ROOT_TAG = "app-root";
|
|
18
|
+
export declare const APP_ROOT_ATTRIBUTES: readonly string[];
|
|
19
|
+
/** The same marks as one CSS selector, for a reader that has a DOM. */
|
|
20
|
+
export declare const APP_ROOT_SELECTOR: string;
|
|
21
|
+
/** "Enable JavaScript" notices, in the words sites use. */
|
|
22
|
+
export declare const JS_NOTICE: RegExp;
|
|
23
|
+
/** Whether one start tag (its name and its attributes by name) is an application root. */
|
|
24
|
+
export declare function isAppRoot(name: string, attributes: ReadonlyMap<string, string>): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type TextFetchResult } from '../net/fetch-text.js';
|
|
2
|
+
import { type RobotsTxt } from '../robots/index.js';
|
|
3
|
+
import type { CheckOptions, RobotsRead, SiteCheck } from './types.js';
|
|
4
|
+
export declare function robotsRead(result: TextFetchResult, url: string): {
|
|
5
|
+
read: RobotsRead;
|
|
6
|
+
robots: RobotsTxt | null;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Checks a site given as a host name (`example.com`) or an http(s) address (only its scheme and
|
|
10
|
+
* host are used). Never throws for what a site does or for an address that cannot be checked:
|
|
11
|
+
* the result says what was read and what was not.
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkSite(input: string, options?: CheckOptions): Promise<SiteCheck>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A registrable domain without a public-suffix list. The library keeps one runtime dependency,
|
|
3
|
+
* so the built-in rule is small: the last two labels, or the last three when the last two are a
|
|
4
|
+
* common second-level suffix (`co.uk`, `com.au`). A caller that needs the full list passes its
|
|
5
|
+
* own function (`CheckOptions.registrableDomain`, `ReadHomePageOptions.registrableDomain`).
|
|
6
|
+
*/
|
|
7
|
+
/** A host name as comparisons use it: lower case, without one trailing dot. */
|
|
8
|
+
export declare function normalizeHost(host: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* The registrable domain of a host name (`www.shop.example.co.uk` → `example.co.uk`). A name
|
|
11
|
+
* with one label, or an IP literal, is returned as it is.
|
|
12
|
+
*/
|
|
13
|
+
export declare function registrableDomain(host: string): string;
|
|
14
|
+
/** Whether `host` is the registrable domain `domain` or a name under it. */
|
|
15
|
+
export declare function isOnDomain(host: string, domain: string): boolean;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Finding, SiteCheck } from './types.js';
|
|
2
|
+
/** The finding ids that exist, for tests and for tools that list them. */
|
|
3
|
+
export declare const FINDING_IDS: readonly string[];
|
|
4
|
+
/** The findings for the facts of a check. */
|
|
5
|
+
export declare function buildFindings(site: Omit<SiteCheck, 'findings'>): Finding[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HomePageFacts } from './types.js';
|
|
2
|
+
/** A home page with at least this many words of main text has "text in the HTML". */
|
|
3
|
+
export declare const TEXT_IN_HTML_MIN_WORDS = 150;
|
|
4
|
+
export interface ReadHomePageOptions {
|
|
5
|
+
/** The values of the `X-Robots-Tag` response header, if any. */
|
|
6
|
+
readonly xRobotsTag?: string | readonly string[] | null;
|
|
7
|
+
/** Maps a host name to its registrable domain; the built-in rule knows common suffixes only. */
|
|
8
|
+
readonly registrableDomain?: (host: string) => string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Whether `X-Robots-Tag` values say noindex to every crawler. A value may start with an agent
|
|
12
|
+
* (`googlebot: noindex, nofollow`), which then applies to the rest of that value up to the next
|
|
13
|
+
* agent; such a rule is for one crawler and does not count. `unavailable_after: <date>` is a
|
|
14
|
+
* directive, not an agent.
|
|
15
|
+
*/
|
|
16
|
+
export declare function xRobotsNoindex(values: string | readonly string[] | null | undefined): boolean;
|
|
17
|
+
/** Reads the facts of a home page. Never throws. */
|
|
18
|
+
export declare function readHomePage(html: string, url: string, options?: ReadHomePageOptions): HomePageFacts;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TextFetchResult } from '../net/fetch-text.js';
|
|
2
|
+
import { type ReadHomePageOptions } from './home-page.js';
|
|
3
|
+
import type { HomeRead } from './types.js';
|
|
4
|
+
/** Reads the answer for the home page. `not_allowed` is not decided here: no request was made. */
|
|
5
|
+
export declare function readHomeResponse(result: TextFetchResult, options?: Pick<ReadHomePageOptions, 'registrableDomain'>): HomeRead;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { JsonLdBound } from './types.js';
|
|
2
|
+
/** Characters of a page scanned; the fetch cap for a page is 2 MiB, so a fetched page fits. */
|
|
3
|
+
export declare const HOME_SCAN_MAX_CHARS: number;
|
|
4
|
+
/** JSON-LD limits: blocks read, one block's size, all blocks' size (in characters). */
|
|
5
|
+
export declare const MAX_LD_BLOCKS = 50;
|
|
6
|
+
export declare const MAX_LD_BLOCK_CHARS: number;
|
|
7
|
+
export declare const MAX_LD_TOTAL_CHARS: number;
|
|
8
|
+
/** What one pass over a page found. */
|
|
9
|
+
export interface PageScan {
|
|
10
|
+
/** Text of `<main>` and `[role=main]`; empty when `hasMain` is false. */
|
|
11
|
+
readonly mainText: string;
|
|
12
|
+
/** Text of the body without `header`, `nav`, `footer` and `aside`. */
|
|
13
|
+
readonly bodyText: string;
|
|
14
|
+
readonly hasMain: boolean;
|
|
15
|
+
/** What a visitor sees (no scripts, styles, templates), cut to 64 KiB. */
|
|
16
|
+
readonly visibleText: string;
|
|
17
|
+
/** Text inside `<noscript>` and `<title>`, cut to 64 KiB. */
|
|
18
|
+
readonly noticeText: string;
|
|
19
|
+
/** `content` of every `<meta name="robots">`. */
|
|
20
|
+
readonly metaRobots: readonly string[];
|
|
21
|
+
/** `itemtype` values, split on spaces, at most 200. */
|
|
22
|
+
readonly itemtypes: readonly string[];
|
|
23
|
+
/** Contents of the `<script type="application/ld+json">` blocks that were read. */
|
|
24
|
+
readonly ldBlocks: readonly string[];
|
|
25
|
+
/** ld+json scripts found, whether or not they were read. */
|
|
26
|
+
readonly ldScripts: number;
|
|
27
|
+
readonly ldBounds: readonly JsonLdBound[];
|
|
28
|
+
/** The input was longer than `HOME_SCAN_MAX_CHARS`. */
|
|
29
|
+
readonly truncated: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Scans a page. Never throws; text it cannot make sense of is left out. */
|
|
32
|
+
export declare function scanPage(input: string): PageScan;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the page has an application root element (see `isAppRoot`): a second, light pass over
|
|
35
|
+
* the start tags that stops at the first one found.
|
|
36
|
+
*/
|
|
37
|
+
export declare function hasAppRoot(input: string): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { APP_ROOT_ATTRIBUTES, APP_ROOT_IDS, APP_ROOT_SELECTOR, APP_ROOT_TAG, EMPTY_SHELL_MAX_WORDS, EMPTY_SHELL_MIN_BYTES, isAppRoot, JS_NOTICE, } from './app-shell.js';
|
|
2
|
+
export { checkSite } from './check-site.js';
|
|
3
|
+
export { registrableDomain } from './domain.js';
|
|
4
|
+
export { buildFindings, FINDING_IDS } from './findings.js';
|
|
5
|
+
export { readHomeResponse } from './home-response.js';
|
|
6
|
+
export { readHomePage, TEXT_IN_HTML_MIN_WORDS, xRobotsNoindex, type ReadHomePageOptions, } from './home-page.js';
|
|
7
|
+
export { CHALLENGE_MARKS, INTERSTITIAL_MARKS, CHALLENGE_MAX_WORDS, HTML_MARKUP, isChallenge, isHomeChallenge, PLACEHOLDER_MARKS, PLACEHOLDER_MAX_WORDS, placeholderMark, } from './marks.js';
|
|
8
|
+
export { PROFILE_HOSTS, PROFILE_KINDS, profileKind } from './profiles.js';
|
|
9
|
+
export { READER_TOKEN } from './reader.js';
|
|
10
|
+
export { SCHEMA_ORG_TYPES } from './schema-org-types.js';
|
|
11
|
+
export { ORGANIZATION_TYPES, SCHEMA_ORG_SHA256, SCHEMA_ORG_VERSION, } from './schema-org-organization-types.js';
|
|
12
|
+
export { MAX_LOCS_COUNTED, readSitemap, readSitemapText, SITEMAP_MAX_BYTES, type ReadSitemapOptions, } from './sitemap.js';
|
|
13
|
+
export type { CheckOptions, CrawlerRootAccess, Finding, FindingLevel, HomeContent, HomeOutcome, HomePageFacts, HomeRead, JsonLdBound, JsonLdFacts, LlmsRead, OrganizationFacts, OrganizationSource, ProfileKind, RobotsOutcome, RobotsRead, SiteCheck, SitemapFacts, SitemapOutcome, SitemapRead, SitemapReason, SitemapSource, } from './types.js';
|
|
14
|
+
export { countWords, MAX_WORDS_COUNTED, type WordCount } from './words.js';
|
|
15
|
+
export { HOME_SCAN_MAX_CHARS, MAX_LD_BLOCK_CHARS, MAX_LD_BLOCKS, MAX_LD_TOTAL_CHARS } from './html.js';
|
|
16
|
+
export { MAX_LD_DEPTH, MAX_LD_NODES } from './json-ld.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the JSON-LD blocks of a page: parses them, keeps those whose top-level `@context` names
|
|
3
|
+
* schema.org, and walks them for typed nodes. The walk is bounded: depth 16, 5,000 nodes. Past
|
|
4
|
+
* a bound the rest is not read and the bound is reported.
|
|
5
|
+
*/
|
|
6
|
+
import type { JsonLdBound } from './types.js';
|
|
7
|
+
export declare const MAX_LD_DEPTH = 16;
|
|
8
|
+
export declare const MAX_LD_NODES = 5000;
|
|
9
|
+
type Json = unknown;
|
|
10
|
+
export type JsonObject = Record<string, Json>;
|
|
11
|
+
/** A node of the data that has an `@type`. */
|
|
12
|
+
export interface TypedNode {
|
|
13
|
+
/** Type names with the schema.org prefixes removed. */
|
|
14
|
+
readonly types: readonly string[];
|
|
15
|
+
readonly node: JsonObject;
|
|
16
|
+
}
|
|
17
|
+
export interface JsonLdWalk {
|
|
18
|
+
readonly scripts: number;
|
|
19
|
+
readonly parsed: number;
|
|
20
|
+
readonly schemaOrgBlocks: number;
|
|
21
|
+
readonly errors: number;
|
|
22
|
+
readonly bounds: readonly JsonLdBound[];
|
|
23
|
+
/** Typed nodes of the blocks that name schema.org, in document order (parents first). */
|
|
24
|
+
readonly typed: readonly TypedNode[];
|
|
25
|
+
}
|
|
26
|
+
/** Whether an `@context` value names schema.org (as a string, in an array, or as `@vocab`). */
|
|
27
|
+
export declare function namesSchemaOrg(context: Json): boolean;
|
|
28
|
+
export declare function isObject(value: Json): value is JsonObject;
|
|
29
|
+
/** A type name without `schema:` and `http(s)://schema.org/`. */
|
|
30
|
+
export declare function schemaTypeName(raw: string): string;
|
|
31
|
+
/** The block's text without a wrapping `<!-- -->` or `<![CDATA[ ]]>`, and without a byte order mark. */
|
|
32
|
+
export declare function unwrapBlock(text: string): string;
|
|
33
|
+
/** Parses the blocks and walks the data that names schema.org. */
|
|
34
|
+
export declare function walkJsonLd(blocks: readonly string[], scripts: number, inheritedBounds: readonly JsonLdBound[]): JsonLdWalk;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** HTML markup anywhere in a body: no robots.txt holds it. */
|
|
2
|
+
export declare const HTML_MARKUP: RegExp;
|
|
3
|
+
/**
|
|
4
|
+
* The marks of a bot check served in place of a file: Cloudflare ("Just a moment…", its
|
|
5
|
+
* challenge platform), reCAPTCHA, hCaptcha, Akamai Bot Manager, HUMAN (PerimeterX), DataDome,
|
|
6
|
+
* Imperva (Incapsula), AWS WAF, Sucuri, a hosting platform's security checkpoint, Kasada, and the
|
|
7
|
+
* generic wording.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CHALLENGE_MARKS: RegExp;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a 2xx answer at /robots.txt is not the file: a 202 (a check still running, most
|
|
12
|
+
* often), or a body with no robots.txt group that is an HTML page or carries the marks of a bot
|
|
13
|
+
* check. Such a site is "not read", never "read, no rules". A body with at least one group is a
|
|
14
|
+
* file whatever its content type says.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isChallenge(status: number | null, body: string): boolean;
|
|
17
|
+
/** A page with fewer words than this, carrying an interstitial mark, is a bot check. */
|
|
18
|
+
export declare const CHALLENGE_MAX_WORDS = 100;
|
|
19
|
+
/** A page with fewer words than this, carrying a mark, is a placeholder. */
|
|
20
|
+
export declare const PLACEHOLDER_MAX_WORDS = 150;
|
|
21
|
+
/** Characters of a page searched for marks: the head and the start of the body. */
|
|
22
|
+
export declare const MARKS_SCAN_CHARS: number;
|
|
23
|
+
/**
|
|
24
|
+
* What a bot-check page says to a visitor, in its title or visible text: Cloudflare's "Just a
|
|
25
|
+
* moment..." and "One more step", "Checking your browser", Imperva's "Pardon Our Interruption",
|
|
26
|
+
* "Verify you are human", "Access denied" beside a captcha, a security checkpoint, and the
|
|
27
|
+
* generic wording. These are for the home page, matched against text a visitor sees, never
|
|
28
|
+
* against script sources: the scripts of a bot-check vendor (`challenge-platform`, a captcha
|
|
29
|
+
* library, `datadome`) are loaded on ordinary pages too, and an empty application shell that
|
|
30
|
+
* loads one is a page, not a check.
|
|
31
|
+
*/
|
|
32
|
+
export declare const INTERSTITIAL_MARKS: RegExp;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the home page is a bot check: a 202, or a small page (under `CHALLENGE_MAX_WORDS`
|
|
35
|
+
* words of text) whose title or visible text (`text`) carries one of `INTERSTITIAL_MARKS`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isHomeChallenge(status: number | null, text: string, mainWords: number): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The marks of a placeholder home page, each a fixed phrase: the default pages of common web
|
|
40
|
+
* servers and control panels, and the wording of domain parking pages. Matched without case
|
|
41
|
+
* against the start of the page's HTML.
|
|
42
|
+
*/
|
|
43
|
+
export declare const PLACEHOLDER_MARKS: readonly {
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly pattern: RegExp;
|
|
46
|
+
}[];
|
|
47
|
+
/**
|
|
48
|
+
* Whether the home page is a placeholder: a small page (under `PLACEHOLDER_MAX_WORDS` words of
|
|
49
|
+
* text) that carries one of `PLACEHOLDER_MARKS`. Returns the mark's id, or null.
|
|
50
|
+
*/
|
|
51
|
+
export declare function placeholderMark(html: string, mainWords: number): string | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Organization of a home page: which typed nodes are one, which is the primary node, and
|
|
3
|
+
* what it says about itself.
|
|
4
|
+
*/
|
|
5
|
+
import { type JsonObject, type TypedNode } from './json-ld.js';
|
|
6
|
+
import type { OrganizationFacts } from './types.js';
|
|
7
|
+
/** Whether a (prefix-free) type name is Organization or one of its schema.org subtypes. */
|
|
8
|
+
export declare function isOrganizationType(name: string): boolean;
|
|
9
|
+
export interface OrganizationNode {
|
|
10
|
+
readonly type: string;
|
|
11
|
+
readonly node: JsonObject;
|
|
12
|
+
}
|
|
13
|
+
/** The Organization nodes among the typed nodes, in document order. */
|
|
14
|
+
export declare function organizationNodes(typed: readonly TypedNode[]): OrganizationNode[];
|
|
15
|
+
/**
|
|
16
|
+
* The primary node: the first Organization node whose `url` is on the site's registrable
|
|
17
|
+
* domain, else the first. Null without any.
|
|
18
|
+
*/
|
|
19
|
+
export declare function primaryOrganization(nodes: readonly OrganizationNode[], siteDomain: string, registrable: (host: string) => string): OrganizationNode | null;
|
|
20
|
+
/** What the primary node says about itself. */
|
|
21
|
+
export declare function describeOrganization(primary: OrganizationNode, pageUrl: string, siteDomain: string, registrable: (host: string) => string): OrganizationFacts;
|
|
22
|
+
/** Whether any `potentialAction` of the node is a `SearchAction`. */
|
|
23
|
+
export declare function hasSearchAction(node: JsonObject): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* robots.txt per origin. An address on a subdomain, or on the other scheme, is a different
|
|
3
|
+
* origin with its own robots.txt: before this tool requests anything there, it reads that
|
|
4
|
+
* origin's file once and asks whether its own token may fetch the path. Answers are kept for
|
|
5
|
+
* the run.
|
|
6
|
+
*/
|
|
7
|
+
import { type RobotsTxt } from '../robots/index.js';
|
|
8
|
+
import { type TextFetcher } from '../net/fetch-text.js';
|
|
9
|
+
export type OriginAccess = 'allowed' | 'closed' | 'unreadable';
|
|
10
|
+
export interface OriginRobots {
|
|
11
|
+
/** The parsed robots.txt of an origin (a missing file is an empty one); null when unreadable. */
|
|
12
|
+
robotsOf(origin: string): Promise<RobotsTxt | null>;
|
|
13
|
+
/** Whether this tool's token may fetch the address, by the robots.txt of the address's origin. */
|
|
14
|
+
check(url: string): Promise<OriginAccess>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createOriginRobots(options: {
|
|
17
|
+
readonly fetcher: TextFetcher;
|
|
18
|
+
readonly signal?: AbortSignal;
|
|
19
|
+
readonly redirectAllowed?: (target: URL) => boolean;
|
|
20
|
+
}): OriginRobots;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProfileKind } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Profile kinds of a `sameAs` link, by host. The list is part of the method: a host is the
|
|
4
|
+
* domain or any name under it (`en.wikipedia.org`, `m.facebook.com`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const PROFILE_HOSTS: readonly {
|
|
7
|
+
readonly kind: Exclude<ProfileKind, 'other'>;
|
|
8
|
+
readonly hosts: readonly string[];
|
|
9
|
+
}[];
|
|
10
|
+
/** The kinds in the order results list them. */
|
|
11
|
+
export declare const PROFILE_KINDS: readonly ProfileKind[];
|
|
12
|
+
/** The kind of the profile at a host name. */
|
|
13
|
+
export declare function profileKind(host: string): ProfileKind;
|