milieu-cli 0.1.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/LICENSE +200 -0
- package/README.md +153 -0
- package/dist/bridges/index.d.ts +5 -0
- package/dist/bridges/index.d.ts.map +1 -0
- package/dist/bridges/index.js +6 -0
- package/dist/bridges/index.js.map +1 -0
- package/dist/bridges/reachability/crawler-policy.d.ts +36 -0
- package/dist/bridges/reachability/crawler-policy.d.ts.map +1 -0
- package/dist/bridges/reachability/crawler-policy.js +110 -0
- package/dist/bridges/reachability/crawler-policy.js.map +1 -0
- package/dist/bridges/reachability/http-status.d.ts +7 -0
- package/dist/bridges/reachability/http-status.d.ts.map +1 -0
- package/dist/bridges/reachability/http-status.js +74 -0
- package/dist/bridges/reachability/http-status.js.map +1 -0
- package/dist/bridges/reachability/https-check.d.ts +14 -0
- package/dist/bridges/reachability/https-check.d.ts.map +1 -0
- package/dist/bridges/reachability/https-check.js +38 -0
- package/dist/bridges/reachability/https-check.js.map +1 -0
- package/dist/bridges/reachability/index.d.ts +13 -0
- package/dist/bridges/reachability/index.d.ts.map +1 -0
- package/dist/bridges/reachability/index.js +115 -0
- package/dist/bridges/reachability/index.js.map +1 -0
- package/dist/bridges/reachability/meta-robots.d.ts +16 -0
- package/dist/bridges/reachability/meta-robots.d.ts.map +1 -0
- package/dist/bridges/reachability/meta-robots.js +119 -0
- package/dist/bridges/reachability/meta-robots.js.map +1 -0
- package/dist/bridges/reachability/robots-parser.d.ts +26 -0
- package/dist/bridges/reachability/robots-parser.d.ts.map +1 -0
- package/dist/bridges/reachability/robots-parser.js +105 -0
- package/dist/bridges/reachability/robots-parser.js.map +1 -0
- package/dist/bridges/reachability/robots-txt.d.ts +14 -0
- package/dist/bridges/reachability/robots-txt.d.ts.map +1 -0
- package/dist/bridges/reachability/robots-txt.js +80 -0
- package/dist/bridges/reachability/robots-txt.js.map +1 -0
- package/dist/bridges/separation/api-presence.d.ts +14 -0
- package/dist/bridges/separation/api-presence.d.ts.map +1 -0
- package/dist/bridges/separation/api-presence.js +96 -0
- package/dist/bridges/separation/api-presence.js.map +1 -0
- package/dist/bridges/separation/developer-docs.d.ts +21 -0
- package/dist/bridges/separation/developer-docs.d.ts.map +1 -0
- package/dist/bridges/separation/developer-docs.js +81 -0
- package/dist/bridges/separation/developer-docs.js.map +1 -0
- package/dist/bridges/separation/index.d.ts +20 -0
- package/dist/bridges/separation/index.d.ts.map +1 -0
- package/dist/bridges/separation/index.js +63 -0
- package/dist/bridges/separation/index.js.map +1 -0
- package/dist/bridges/separation/sdk-references.d.ts +12 -0
- package/dist/bridges/separation/sdk-references.d.ts.map +1 -0
- package/dist/bridges/separation/sdk-references.js +93 -0
- package/dist/bridges/separation/sdk-references.js.map +1 -0
- package/dist/bridges/separation/webhook-support.d.ts +19 -0
- package/dist/bridges/separation/webhook-support.d.ts.map +1 -0
- package/dist/bridges/separation/webhook-support.js +94 -0
- package/dist/bridges/separation/webhook-support.js.map +1 -0
- package/dist/bridges/standards/index.d.ts +13 -0
- package/dist/bridges/standards/index.d.ts.map +1 -0
- package/dist/bridges/standards/index.js +79 -0
- package/dist/bridges/standards/index.js.map +1 -0
- package/dist/bridges/standards/json-ld.d.ts +16 -0
- package/dist/bridges/standards/json-ld.d.ts.map +1 -0
- package/dist/bridges/standards/json-ld.js +63 -0
- package/dist/bridges/standards/json-ld.js.map +1 -0
- package/dist/bridges/standards/llms-txt.d.ts +19 -0
- package/dist/bridges/standards/llms-txt.d.ts.map +1 -0
- package/dist/bridges/standards/llms-txt.js +64 -0
- package/dist/bridges/standards/llms-txt.js.map +1 -0
- package/dist/bridges/standards/mcp.d.ts +13 -0
- package/dist/bridges/standards/mcp.d.ts.map +1 -0
- package/dist/bridges/standards/mcp.js +72 -0
- package/dist/bridges/standards/mcp.js.map +1 -0
- package/dist/bridges/standards/openapi.d.ts +14 -0
- package/dist/bridges/standards/openapi.d.ts.map +1 -0
- package/dist/bridges/standards/openapi.js +424 -0
- package/dist/bridges/standards/openapi.js.map +1 -0
- package/dist/bridges/standards/schema-org.d.ts +12 -0
- package/dist/bridges/standards/schema-org.d.ts.map +1 -0
- package/dist/bridges/standards/schema-org.js +101 -0
- package/dist/bridges/standards/schema-org.js.map +1 -0
- package/dist/bridges/standards/well-known.d.ts +16 -0
- package/dist/bridges/standards/well-known.d.ts.map +1 -0
- package/dist/bridges/standards/well-known.js +77 -0
- package/dist/bridges/standards/well-known.js.map +1 -0
- package/dist/bridges/stubs.d.ts +4 -0
- package/dist/bridges/stubs.d.ts.map +1 -0
- package/dist/bridges/stubs.js +25 -0
- package/dist/bridges/stubs.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +83 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/explanations.d.ts +11 -0
- package/dist/core/explanations.d.ts.map +1 -0
- package/dist/core/explanations.js +128 -0
- package/dist/core/explanations.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/scan.d.ts +3 -0
- package/dist/core/scan.d.ts.map +1 -0
- package/dist/core/scan.js +89 -0
- package/dist/core/scan.js.map +1 -0
- package/dist/core/types.d.ts +119 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +7 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/render/colors.d.ts +7 -0
- package/dist/render/colors.d.ts.map +1 -0
- package/dist/render/colors.js +28 -0
- package/dist/render/colors.js.map +1 -0
- package/dist/render/format-bridge.d.ts +3 -0
- package/dist/render/format-bridge.d.ts.map +1 -0
- package/dist/render/format-bridge.js +39 -0
- package/dist/render/format-bridge.js.map +1 -0
- package/dist/render/format-scan.d.ts +3 -0
- package/dist/render/format-scan.d.ts.map +1 -0
- package/dist/render/format-scan.js +44 -0
- package/dist/render/format-scan.js.map +1 -0
- package/dist/render/format-verbose.d.ts +3 -0
- package/dist/render/format-verbose.d.ts.map +1 -0
- package/dist/render/format-verbose.js +14 -0
- package/dist/render/format-verbose.js.map +1 -0
- package/dist/render/index.d.ts +7 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +8 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/progress-bar.d.ts +10 -0
- package/dist/render/progress-bar.d.ts.map +1 -0
- package/dist/render/progress-bar.js +21 -0
- package/dist/render/progress-bar.js.map +1 -0
- package/dist/render/symbols.d.ts +10 -0
- package/dist/render/symbols.d.ts.map +1 -0
- package/dist/render/symbols.js +21 -0
- package/dist/render/symbols.js.map +1 -0
- package/dist/utils/http-client.d.ts +25 -0
- package/dist/utils/http-client.d.ts.map +1 -0
- package/dist/utils/http-client.js +235 -0
- package/dist/utils/http-client.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/ssrf.d.ts +29 -0
- package/dist/utils/ssrf.d.ts.map +1 -0
- package/dist/utils/ssrf.js +134 -0
- package/dist/utils/ssrf.js.map +1 -0
- package/dist/utils/url.d.ts +53 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/url.js +64 -0
- package/dist/utils/url.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stubs.d.ts","sourceRoot":"","sources":["../../src/bridges/stubs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,wBAAgB,iBAAiB,IAAI,YAAY,CAYhD;AAED,wBAAgB,iBAAiB,IAAI,YAAY,CAYhD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function createBridge4Stub() {
|
|
2
|
+
return {
|
|
3
|
+
id: 4,
|
|
4
|
+
name: "Schema",
|
|
5
|
+
status: "not_evaluated",
|
|
6
|
+
score: null,
|
|
7
|
+
scoreLabel: null,
|
|
8
|
+
checks: [],
|
|
9
|
+
durationMs: 0,
|
|
10
|
+
message: "Schema quality assessment requires deeper analysis beyond automated checks.",
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function createBridge5Stub() {
|
|
14
|
+
return {
|
|
15
|
+
id: 5,
|
|
16
|
+
name: "Context",
|
|
17
|
+
status: "not_evaluated",
|
|
18
|
+
score: null,
|
|
19
|
+
scoreLabel: null,
|
|
20
|
+
checks: [],
|
|
21
|
+
durationMs: 0,
|
|
22
|
+
message: "Context evaluation requires deeper analysis beyond automated checks.",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=stubs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stubs.js","sourceRoot":"","sources":["../../src/bridges/stubs.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,EAAE,EAAE,CAAC;QACL,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,eAAe;QACvB,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,EAAE;QACV,UAAU,EAAE,CAAC;QACb,OAAO,EACL,6EAA6E;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,EAAE,EAAE,CAAC;QACL,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,eAAe;QACvB,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,EAAE;QACV,UAAU,EAAE,CAAC;QACb,OAAO,EACL,sEAAsE;KACzE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,YAAY,IAAI,OAAO,CAwEtC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { scan } from "../core/scan.js";
|
|
4
|
+
import { getVersion } from "../core/version.js";
|
|
5
|
+
import { resolveExplanation } from "../core/explanations.js";
|
|
6
|
+
import { formatScanOutput } from "../render/format-scan.js";
|
|
7
|
+
export function buildProgram() {
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name("milieu")
|
|
11
|
+
.description("Measure how legible your product is to AI agents")
|
|
12
|
+
.version(getVersion());
|
|
13
|
+
program
|
|
14
|
+
.command("scan")
|
|
15
|
+
.description("Scan a URL for AI agent legibility")
|
|
16
|
+
.argument("<url>", "URL to scan")
|
|
17
|
+
.option("--json", "Output result as JSON")
|
|
18
|
+
.option("--pretty", "Pretty-print JSON output (use with --json)")
|
|
19
|
+
.option("--timeout <ms>", "Per-request timeout in milliseconds", "10000")
|
|
20
|
+
.option("--threshold <score>", "Exit non-zero if overall score below threshold")
|
|
21
|
+
.option("--verbose", "Show individual check details")
|
|
22
|
+
.option("--explain-all", "Show explanations on all checks, not just failures (use with --verbose)")
|
|
23
|
+
.option("--quiet", "Suppress terminal output")
|
|
24
|
+
.action(async (url, opts) => {
|
|
25
|
+
const jsonMode = Boolean(opts.json);
|
|
26
|
+
const quiet = Boolean(opts.quiet);
|
|
27
|
+
const verbose = Boolean(opts.verbose);
|
|
28
|
+
const explainAll = Boolean(opts.explainAll);
|
|
29
|
+
const timeout = Number(opts.timeout) || 10_000;
|
|
30
|
+
const threshold = opts.threshold !== undefined ? Number(opts.threshold) : undefined;
|
|
31
|
+
try {
|
|
32
|
+
const result = await scan(url, {
|
|
33
|
+
timeout,
|
|
34
|
+
verbose,
|
|
35
|
+
silent: jsonMode || quiet,
|
|
36
|
+
});
|
|
37
|
+
// Attach "why this matters" explanations to each check
|
|
38
|
+
for (const bridge of result.bridges) {
|
|
39
|
+
for (const check of bridge.checks) {
|
|
40
|
+
check.why = resolveExplanation(check.id, check.status);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (jsonMode) {
|
|
44
|
+
const output = opts.pretty
|
|
45
|
+
? JSON.stringify(result, null, 2)
|
|
46
|
+
: JSON.stringify(result);
|
|
47
|
+
process.stdout.write(output + "\n");
|
|
48
|
+
}
|
|
49
|
+
else if (!quiet) {
|
|
50
|
+
console.log(formatScanOutput(result, verbose, explainAll));
|
|
51
|
+
}
|
|
52
|
+
if (threshold !== undefined && result.overallScore < threshold) {
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
58
|
+
if (jsonMode) {
|
|
59
|
+
const errorObj = { error: message, version: getVersion() };
|
|
60
|
+
process.stdout.write(JSON.stringify(errorObj) + "\n");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
64
|
+
}
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return program;
|
|
69
|
+
}
|
|
70
|
+
// Run when executed directly — resolves symlinks so npx works
|
|
71
|
+
import { fileURLToPath } from "node:url";
|
|
72
|
+
import { realpathSync } from "node:fs";
|
|
73
|
+
try {
|
|
74
|
+
const entry = process.argv[1] ? realpathSync(process.argv[1]) : "";
|
|
75
|
+
if (entry === fileURLToPath(import.meta.url)) {
|
|
76
|
+
const program = buildProgram();
|
|
77
|
+
await program.parseAsync(process.argv);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Not the main module (e.g., imported for testing)
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CheckStatus } from "./types.js";
|
|
2
|
+
/** An explanation can be a plain string or status-dependent */
|
|
3
|
+
export type ExplanationEntry = string | Partial<Record<CheckStatus | "default", string>>;
|
|
4
|
+
/** Static explanations for why each check matters for AI agent readiness */
|
|
5
|
+
export declare const CHECK_EXPLANATIONS: Record<string, ExplanationEntry>;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the explanation for a check given its ID and status.
|
|
8
|
+
* Falls back: status-specific → "default" key → plain string → undefined.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveExplanation(checkId: string, status: CheckStatus): string | undefined;
|
|
11
|
+
//# sourceMappingURL=explanations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explanations.d.ts","sourceRoot":"","sources":["../../src/core/explanations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,+DAA+D;AAC/D,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,OAAO,CAAC,MAAM,CAAC,WAAW,GAAG,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;AAErD,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAuH/D,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,MAAM,GAAG,SAAS,CAKpB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/** Static explanations for why each check matters for AI agent readiness */
|
|
2
|
+
export const CHECK_EXPLANATIONS = {
|
|
3
|
+
// Bridge 1: Reachability
|
|
4
|
+
https_available: "HTTPS is required for secure communication. AI agents refuse to interact with insecure endpoints.",
|
|
5
|
+
http_status: {
|
|
6
|
+
pass: "Your site returns a clean 200 response — agents can reach your content without issues.",
|
|
7
|
+
fail: "Agents can't reach your content. Non-200 responses block automated workflows entirely.",
|
|
8
|
+
partial: "Redirects add latency and may break automated agent workflows.",
|
|
9
|
+
default: "A non-200 status means agents can't reliably reach your content.",
|
|
10
|
+
},
|
|
11
|
+
robots_txt: {
|
|
12
|
+
pass: "Your robots.txt gives AI agents clear crawling guidance.",
|
|
13
|
+
fail: "Without robots.txt, AI agents have no guidance on what they can access — most default to cautious behavior and skip your site.",
|
|
14
|
+
partial: "Your robots.txt exists but may not provide clear guidance to AI agents.",
|
|
15
|
+
default: "robots.txt tells AI agents what they're allowed to crawl on your site.",
|
|
16
|
+
},
|
|
17
|
+
crawler_policy_gptbot: {
|
|
18
|
+
pass: "GPTBot can access your content — it will be available to ChatGPT and OpenAI's APIs.",
|
|
19
|
+
fail: "Blocking GPTBot prevents your content from being used by ChatGPT and OpenAI's APIs.",
|
|
20
|
+
partial: "GPTBot has restricted access — some of your content may not reach ChatGPT and OpenAI's APIs.",
|
|
21
|
+
default: "GPTBot is OpenAI's crawler. Its policy determines your visibility in the OpenAI ecosystem.",
|
|
22
|
+
},
|
|
23
|
+
crawler_policy_claudebot: {
|
|
24
|
+
pass: "ClaudeBot can access your content — it will be available to Claude.",
|
|
25
|
+
fail: "Blocking ClaudeBot prevents your content from being accessible to Claude.",
|
|
26
|
+
partial: "ClaudeBot has restricted access — some of your content may not be accessible to Claude.",
|
|
27
|
+
default: "ClaudeBot is Anthropic's crawler. Its policy determines your visibility to Claude.",
|
|
28
|
+
},
|
|
29
|
+
crawler_policy_ccbot: {
|
|
30
|
+
pass: "CCBot can access your content — it will be included in the Common Crawl open dataset used to train AI models.",
|
|
31
|
+
fail: "Blocking CCBot removes your content from the largest open web dataset used to train AI models.",
|
|
32
|
+
partial: "CCBot has restricted access — some of your content may be excluded from AI training datasets.",
|
|
33
|
+
default: "CCBot is Common Crawl's bot. Its policy affects whether your content appears in AI training data.",
|
|
34
|
+
},
|
|
35
|
+
crawler_policy_googlebot: {
|
|
36
|
+
pass: "Googlebot can access your content — it will appear in Google Search and Gemini.",
|
|
37
|
+
fail: "Blocking Googlebot removes your content from Google Search results and Gemini.",
|
|
38
|
+
partial: "Googlebot has restricted access — some of your content may not appear in Google Search or Gemini.",
|
|
39
|
+
default: "Googlebot powers Google Search and AI features. Its policy determines your Google visibility.",
|
|
40
|
+
},
|
|
41
|
+
crawler_policy_bingbot: {
|
|
42
|
+
pass: "Bingbot can access your content — it will appear in Bing Search and Microsoft Copilot.",
|
|
43
|
+
fail: "Blocking Bingbot removes your content from Bing Search and Microsoft Copilot.",
|
|
44
|
+
partial: "Bingbot has restricted access — some of your content may not appear in Bing or Copilot.",
|
|
45
|
+
default: "Bingbot powers Bing Search and Microsoft Copilot. Its policy determines your Microsoft AI visibility.",
|
|
46
|
+
},
|
|
47
|
+
crawler_policy_perplexitybot: {
|
|
48
|
+
pass: "PerplexityBot can access your content — it will appear in Perplexity AI search answers.",
|
|
49
|
+
fail: "Blocking PerplexityBot prevents your content from appearing in AI-powered search answers.",
|
|
50
|
+
partial: "PerplexityBot has restricted access — some of your content may not appear in Perplexity answers.",
|
|
51
|
+
default: "PerplexityBot powers Perplexity AI search. Its policy affects your visibility in AI search.",
|
|
52
|
+
},
|
|
53
|
+
meta_robots: "Meta robots tags can override robots.txt — preventing AI agents from indexing or following links even when crawling is allowed.",
|
|
54
|
+
x_robots_tag: "X-Robots-Tag headers apply indexing restrictions at the server level, affecting all AI agents regardless of page content.",
|
|
55
|
+
// Bridge 2: Standards
|
|
56
|
+
openapi_spec: {
|
|
57
|
+
pass: "Your OpenAPI spec lets AI agents discover and correctly call your API endpoints automatically.",
|
|
58
|
+
fail: "Without an OpenAPI spec, AI agents can't discover or correctly call your endpoints — they're locked out of programmatic access.",
|
|
59
|
+
partial: "Your OpenAPI spec was detected but may not be fully parseable by AI agents.",
|
|
60
|
+
default: "An OpenAPI spec is the machine-readable contract that lets AI agents use your API.",
|
|
61
|
+
},
|
|
62
|
+
llms_txt: {
|
|
63
|
+
pass: "Your llms.txt helps AI agents understand what your site offers without crawling every page.",
|
|
64
|
+
fail: "Without llms.txt, AI agents must crawl your entire site to understand what you offer — most won't bother.",
|
|
65
|
+
partial: "Your llms.txt exists but may not follow the expected format for optimal AI consumption.",
|
|
66
|
+
default: "llms.txt provides a structured overview of your site purpose-built for large language models.",
|
|
67
|
+
},
|
|
68
|
+
llms_full_txt: {
|
|
69
|
+
pass: "Your llms-full.txt gives AI agents comprehensive content for deep understanding.",
|
|
70
|
+
fail: "Without llms-full.txt, AI agents only have the summary from llms.txt — they lack the depth needed for detailed answers about your product.",
|
|
71
|
+
default: "llms-full.txt provides comprehensive site content that gives AI agents deep context beyond the llms.txt summary.",
|
|
72
|
+
},
|
|
73
|
+
mcp_endpoint: {
|
|
74
|
+
pass: "Your MCP endpoint lets AI agents connect to your service as a tool — the emerging standard for AI-to-service integration.",
|
|
75
|
+
fail: "Without an MCP endpoint, AI agents can't integrate with your service as a tool provider.",
|
|
76
|
+
default: "MCP (Model Context Protocol) is the emerging standard for AI agents to connect with services as tools.",
|
|
77
|
+
},
|
|
78
|
+
json_ld: {
|
|
79
|
+
pass: "Your JSON-LD lets AI agents extract structured entities, relationships, and attributes from your pages.",
|
|
80
|
+
fail: "Without JSON-LD, AI agents can only read your text — they can't extract structured meaning from your content.",
|
|
81
|
+
default: "JSON-LD structured data helps AI agents understand what your content means, not just what it says.",
|
|
82
|
+
},
|
|
83
|
+
schema_org: {
|
|
84
|
+
pass: "Your Schema.org markup gives AI agents a shared vocabulary to extract structured data from your pages.",
|
|
85
|
+
fail: "Without Schema.org markup, AI agents lack a standard vocabulary for understanding your content's structure.",
|
|
86
|
+
default: "Schema.org provides the shared vocabulary AI agents use to interpret structured data on your pages.",
|
|
87
|
+
},
|
|
88
|
+
security_txt: "security.txt tells AI agents and automated systems how to report security issues. Its presence signals operational maturity.",
|
|
89
|
+
ai_plugin: {
|
|
90
|
+
pass: "Your ai-plugin.json lets ChatGPT and compatible AI agents discover and use your API as a plugin.",
|
|
91
|
+
fail: "Without ai-plugin.json, ChatGPT and compatible AI agents can't discover your API as a plugin.",
|
|
92
|
+
default: "ai-plugin.json is the manifest that lets AI agents like ChatGPT use your API as a plugin.",
|
|
93
|
+
},
|
|
94
|
+
// Bridge 3: Separation
|
|
95
|
+
api_presence: {
|
|
96
|
+
pass: "AI agents can see that your product has a programmatic interface — they'll attempt to integrate with it.",
|
|
97
|
+
fail: "Without API presence signals, AI agents treat your site as content-only — they won't attempt programmatic integration.",
|
|
98
|
+
default: "API presence signals tell AI agents your product has a programmatic interface, not just web pages.",
|
|
99
|
+
},
|
|
100
|
+
developer_docs: {
|
|
101
|
+
pass: "Your developer documentation gives AI agents the information they need to build integrations with your product.",
|
|
102
|
+
fail: "Without developer documentation, AI agents can't learn how to integrate with your product.",
|
|
103
|
+
default: "Developer documentation is where AI agents learn how to build integrations with your product.",
|
|
104
|
+
},
|
|
105
|
+
sdk_references: {
|
|
106
|
+
pass: "AI agents can see which languages your API supports and how to install the client libraries.",
|
|
107
|
+
fail: "Without SDK references, AI agents can't determine which languages you support or how to install your client libraries.",
|
|
108
|
+
default: "SDK and package references tell AI agents which languages your API supports and how to get started.",
|
|
109
|
+
},
|
|
110
|
+
webhook_support: {
|
|
111
|
+
pass: "Your webhook support lets AI agents build reactive workflows that respond to events in real-time.",
|
|
112
|
+
fail: "Without webhook signals, AI agents must poll your API for changes — a slower, less efficient integration pattern.",
|
|
113
|
+
default: "Webhooks enable AI agents to receive real-time events instead of polling your API.",
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Resolve the explanation for a check given its ID and status.
|
|
118
|
+
* Falls back: status-specific → "default" key → plain string → undefined.
|
|
119
|
+
*/
|
|
120
|
+
export function resolveExplanation(checkId, status) {
|
|
121
|
+
const entry = CHECK_EXPLANATIONS[checkId];
|
|
122
|
+
if (entry === undefined)
|
|
123
|
+
return undefined;
|
|
124
|
+
if (typeof entry === "string")
|
|
125
|
+
return entry;
|
|
126
|
+
return entry[status] ?? entry.default;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=explanations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explanations.js","sourceRoot":"","sources":["../../src/core/explanations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export { scan } from "./scan.js";
|
|
3
|
+
export { getVersion } from "./version.js";
|
|
4
|
+
export { resolveExplanation, CHECK_EXPLANATIONS } from "./explanations.js";
|
|
5
|
+
export type { ExplanationEntry } from "./explanations.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Core barrel: public API surface (Phase 8 -- programmatic API contract)
|
|
2
|
+
export * from "./types.js";
|
|
3
|
+
export { scan } from "./scan.js";
|
|
4
|
+
export { getVersion } from "./version.js";
|
|
5
|
+
export { resolveExplanation, CHECK_EXPLANATIONS } from "./explanations.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/core/scan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EAEV,WAAW,EAEZ,MAAM,YAAY,CAAC;AAWpB,wBAAsB,IAAI,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC,CAkGrB"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import { normalizeUrl } from "../utils/index.js";
|
|
3
|
+
import { runReachabilityBridge, runStandardsBridge, runSeparationBridge, createBridge4Stub, createBridge5Stub, } from "../bridges/index.js";
|
|
4
|
+
import { getVersion } from "./version.js";
|
|
5
|
+
export async function scan(url, options = {}) {
|
|
6
|
+
const start = performance.now();
|
|
7
|
+
// Normalize URL
|
|
8
|
+
const normalized = normalizeUrl(url);
|
|
9
|
+
if (!normalized.ok) {
|
|
10
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
11
|
+
}
|
|
12
|
+
const { domain, baseUrl } = normalized;
|
|
13
|
+
const ctx = {
|
|
14
|
+
url,
|
|
15
|
+
domain,
|
|
16
|
+
baseUrl,
|
|
17
|
+
options,
|
|
18
|
+
shared: {},
|
|
19
|
+
};
|
|
20
|
+
const isSilent = options.silent ?? false;
|
|
21
|
+
const spinner = ora({ text: "Scanning...", color: "cyan", isSilent }).start();
|
|
22
|
+
try {
|
|
23
|
+
// Bridge 1: Reachability
|
|
24
|
+
spinner.text = "Bridge 1: Reachability...";
|
|
25
|
+
const bridge1 = await runReachabilityBridge(ctx);
|
|
26
|
+
let bridge2;
|
|
27
|
+
let bridge3;
|
|
28
|
+
if (bridge1.abort) {
|
|
29
|
+
// Fatal error -- skip remaining bridges
|
|
30
|
+
spinner.text = "Scan aborted: " + (bridge1.abortReason ?? "unreachable");
|
|
31
|
+
bridge2 = {
|
|
32
|
+
id: 2,
|
|
33
|
+
name: "Standards",
|
|
34
|
+
status: "evaluated",
|
|
35
|
+
score: 0,
|
|
36
|
+
scoreLabel: "fail",
|
|
37
|
+
checks: [],
|
|
38
|
+
durationMs: 0,
|
|
39
|
+
};
|
|
40
|
+
bridge3 = {
|
|
41
|
+
id: 3,
|
|
42
|
+
name: "Separation",
|
|
43
|
+
status: "evaluated",
|
|
44
|
+
score: null,
|
|
45
|
+
scoreLabel: null,
|
|
46
|
+
checks: [],
|
|
47
|
+
durationMs: 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Bridge 2: Standards
|
|
52
|
+
spinner.text = "Bridge 2: Standards...";
|
|
53
|
+
bridge2 = await runStandardsBridge(ctx);
|
|
54
|
+
// Bridge 3: Separation
|
|
55
|
+
spinner.text = "Bridge 3: Separation...";
|
|
56
|
+
bridge3 = await runSeparationBridge(ctx);
|
|
57
|
+
}
|
|
58
|
+
// Bridges 4-5: Stubs
|
|
59
|
+
const bridge4 = createBridge4Stub();
|
|
60
|
+
const bridge5 = createBridge5Stub();
|
|
61
|
+
// Calculate overall score (average of scored bridges where score is not null)
|
|
62
|
+
const scoredBridges = [bridge1, bridge2, bridge3, bridge4, bridge5].filter((b) => b.score !== null);
|
|
63
|
+
const overallScore = scoredBridges.length > 0
|
|
64
|
+
? Math.round(scoredBridges.reduce((sum, b) => sum + b.score, 0) /
|
|
65
|
+
scoredBridges.length)
|
|
66
|
+
: 0;
|
|
67
|
+
const overallScoreLabel = overallScore >= 80
|
|
68
|
+
? "pass"
|
|
69
|
+
: overallScore >= 40
|
|
70
|
+
? "partial"
|
|
71
|
+
: "fail";
|
|
72
|
+
const result = {
|
|
73
|
+
version: getVersion(),
|
|
74
|
+
url,
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
durationMs: Math.round(performance.now() - start),
|
|
77
|
+
overallScore,
|
|
78
|
+
overallScoreLabel,
|
|
79
|
+
bridges: [bridge1, bridge2, bridge3, bridge4, bridge5],
|
|
80
|
+
};
|
|
81
|
+
spinner.stop();
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
spinner.fail("Scan failed");
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/core/scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** Status of an individual check within a bridge */
|
|
2
|
+
export type CheckStatus = "pass" | "partial" | "fail" | "error";
|
|
3
|
+
/** A single check result */
|
|
4
|
+
export interface Check {
|
|
5
|
+
/** Machine-readable check identifier, e.g. "https_available", "robots_txt_present" */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Human-readable label for display */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Check outcome */
|
|
10
|
+
status: CheckStatus;
|
|
11
|
+
/** Optional detail string shown in verbose mode */
|
|
12
|
+
detail?: string;
|
|
13
|
+
/** Optional structured data for JSON output (e.g., crawler directives, schema types) */
|
|
14
|
+
data?: Record<string, unknown>;
|
|
15
|
+
/** Why this check result matters for AI agent readiness */
|
|
16
|
+
why?: string;
|
|
17
|
+
}
|
|
18
|
+
/** A text content blob with its source label, used for multi-source signal scanning */
|
|
19
|
+
export interface ContentSource {
|
|
20
|
+
/** The text content to scan */
|
|
21
|
+
content: string;
|
|
22
|
+
/** Human-readable source label (e.g., "homepage", "llms.txt", "/docs") */
|
|
23
|
+
source: string;
|
|
24
|
+
}
|
|
25
|
+
/** Bridge identifiers (1-5) */
|
|
26
|
+
export type BridgeId = 1 | 2 | 3 | 4 | 5;
|
|
27
|
+
/** Human-readable bridge names */
|
|
28
|
+
export type BridgeName = "Reachability" | "Standards" | "Separation" | "Schema" | "Context";
|
|
29
|
+
/** Bridge evaluation status */
|
|
30
|
+
export type BridgeStatus = "evaluated" | "not_evaluated";
|
|
31
|
+
/** Result of a single bridge assessment */
|
|
32
|
+
export interface BridgeResult {
|
|
33
|
+
/** Bridge number (1-5) */
|
|
34
|
+
id: BridgeId;
|
|
35
|
+
/** Bridge name */
|
|
36
|
+
name: BridgeName;
|
|
37
|
+
/** Whether this bridge was evaluated or is a stub */
|
|
38
|
+
status: BridgeStatus;
|
|
39
|
+
/** Score 0-100 for scored bridges (1, 2), null for detection-only (3) and stubs (4, 5) */
|
|
40
|
+
score: number | null;
|
|
41
|
+
/** Score category for scored bridges, null for unscored */
|
|
42
|
+
scoreLabel: "pass" | "partial" | "fail" | null;
|
|
43
|
+
/** Individual checks within this bridge, empty array for stubs */
|
|
44
|
+
checks: Check[];
|
|
45
|
+
/** Time in milliseconds to evaluate this bridge */
|
|
46
|
+
durationMs: number;
|
|
47
|
+
/** Human-readable message for stubs (bridges 4-5) */
|
|
48
|
+
message?: string;
|
|
49
|
+
/** If true, scan should abort -- no further bridges attempted */
|
|
50
|
+
abort?: boolean;
|
|
51
|
+
/** Reason for abort (dns, connection_refused, ssl_error) */
|
|
52
|
+
abortReason?: string;
|
|
53
|
+
}
|
|
54
|
+
/** Options passed to the scan function */
|
|
55
|
+
export interface ScanOptions {
|
|
56
|
+
/** Per-request HTTP timeout in milliseconds (default: 10000) */
|
|
57
|
+
timeout?: number;
|
|
58
|
+
/** Show verbose output with individual check details */
|
|
59
|
+
verbose?: boolean;
|
|
60
|
+
/** Suppress spinner and terminal output (for JSON/quiet modes) */
|
|
61
|
+
silent?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** Context shared across bridge checks during a single scan */
|
|
64
|
+
export interface ScanContext {
|
|
65
|
+
/** The original URL provided by the user */
|
|
66
|
+
url: string;
|
|
67
|
+
/** Normalized domain extracted from URL (e.g., "example.com") */
|
|
68
|
+
domain: string;
|
|
69
|
+
/** Base URL with protocol (e.g., "https://example.com") */
|
|
70
|
+
baseUrl: string;
|
|
71
|
+
/** Scan options */
|
|
72
|
+
options: ScanOptions;
|
|
73
|
+
/** Shared data between bridges (e.g., Bridge 2 OpenAPI result reused by Bridge 3) */
|
|
74
|
+
shared: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
/** Complete scan result -- this is the JSON output public API contract */
|
|
77
|
+
export interface ScanResult {
|
|
78
|
+
/** Schema version for JSON output stability (semver) */
|
|
79
|
+
version: string;
|
|
80
|
+
/** The URL that was scanned */
|
|
81
|
+
url: string;
|
|
82
|
+
/** ISO 8601 timestamp when scan started */
|
|
83
|
+
timestamp: string;
|
|
84
|
+
/** Total scan duration in milliseconds */
|
|
85
|
+
durationMs: number;
|
|
86
|
+
/** Overall score (0-100) averaged from scored bridges only (1, 2) */
|
|
87
|
+
overallScore: number;
|
|
88
|
+
/** Overall score category */
|
|
89
|
+
overallScoreLabel: "pass" | "partial" | "fail";
|
|
90
|
+
/** Results for each bridge (always 5 entries, in order) */
|
|
91
|
+
bridges: [BridgeResult, BridgeResult, BridgeResult, BridgeResult, BridgeResult];
|
|
92
|
+
}
|
|
93
|
+
/** Discriminated union for HTTP client errors (FOUND-03) */
|
|
94
|
+
export type HttpErrorKind = "dns" | "timeout" | "ssrf_blocked" | "http_error" | "bot_protected" | "connection_refused" | "ssl_error" | "body_too_large" | "unknown";
|
|
95
|
+
/** HTTP error with discriminated kind field */
|
|
96
|
+
export interface HttpError {
|
|
97
|
+
kind: HttpErrorKind;
|
|
98
|
+
message: string;
|
|
99
|
+
statusCode?: number;
|
|
100
|
+
url: string;
|
|
101
|
+
}
|
|
102
|
+
/** HTTP success response */
|
|
103
|
+
export interface HttpSuccess {
|
|
104
|
+
ok: true;
|
|
105
|
+
url: string;
|
|
106
|
+
status: number;
|
|
107
|
+
headers: Record<string, string>;
|
|
108
|
+
body: string;
|
|
109
|
+
redirects: string[];
|
|
110
|
+
durationMs: number;
|
|
111
|
+
}
|
|
112
|
+
/** HTTP failure response */
|
|
113
|
+
export interface HttpFailure {
|
|
114
|
+
ok: false;
|
|
115
|
+
error: HttpError;
|
|
116
|
+
}
|
|
117
|
+
/** Discriminated union for HTTP responses */
|
|
118
|
+
export type HttpResponse = HttpSuccess | HttpFailure;
|
|
119
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,uBAAuB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAKA,wBAAgB,UAAU,IAAI,MAAM,CAEnC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAC;AAEjE,MAAM,UAAU,UAAU;IACxB,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,6CAA6C;AAE7C,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function green(text: string): string;
|
|
2
|
+
export declare function yellow(text: string): string;
|
|
3
|
+
export declare function red(text: string): string;
|
|
4
|
+
export declare function cyan(text: string): string;
|
|
5
|
+
export declare function dim(text: string): string;
|
|
6
|
+
export declare function bold(text: string): string;
|
|
7
|
+
//# sourceMappingURL=colors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/render/colors.ts"],"names":[],"mappings":"AAYA,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1C;AACD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AACD,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AACD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzC;AACD,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AACD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzC"}
|