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,134 @@
|
|
|
1
|
+
import dns from "node:dns/promises";
|
|
2
|
+
import net from "node:net";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// IPv4 private/reserved range checks
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function isPrivateIPv4(ip) {
|
|
7
|
+
const parts = ip.split(".").map(Number);
|
|
8
|
+
if (parts.length !== 4 || parts.some((p) => isNaN(p)))
|
|
9
|
+
return false;
|
|
10
|
+
const [a, b] = parts;
|
|
11
|
+
// 10.0.0.0/8
|
|
12
|
+
if (a === 10)
|
|
13
|
+
return true;
|
|
14
|
+
// 172.16.0.0/12 (172.16.x.x - 172.31.x.x)
|
|
15
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
16
|
+
return true;
|
|
17
|
+
// 192.168.0.0/16
|
|
18
|
+
if (a === 192 && b === 168)
|
|
19
|
+
return true;
|
|
20
|
+
// 127.0.0.0/8 (loopback)
|
|
21
|
+
if (a === 127)
|
|
22
|
+
return true;
|
|
23
|
+
// 0.0.0.0/8
|
|
24
|
+
if (a === 0)
|
|
25
|
+
return true;
|
|
26
|
+
// 169.254.0.0/16 (link-local, includes AWS metadata 169.254.169.254)
|
|
27
|
+
if (a === 169 && b === 254)
|
|
28
|
+
return true;
|
|
29
|
+
// 100.64.0.0/10 (CGNAT: 100.64.x.x - 100.127.x.x)
|
|
30
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
31
|
+
return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// IPv6 private/reserved range checks
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
function isPrivateIPv6(ip) {
|
|
38
|
+
const lower = ip.toLowerCase();
|
|
39
|
+
// IPv4-mapped IPv6 (::ffff:x.x.x.x) -- extract and check IPv4 portion
|
|
40
|
+
if (lower.startsWith("::ffff:")) {
|
|
41
|
+
const v4Part = lower.slice(7);
|
|
42
|
+
if (net.isIPv4(v4Part))
|
|
43
|
+
return isPrivateIPv4(v4Part);
|
|
44
|
+
}
|
|
45
|
+
// ::1 loopback
|
|
46
|
+
if (lower === "::1")
|
|
47
|
+
return true;
|
|
48
|
+
// :: unspecified
|
|
49
|
+
if (lower === "::")
|
|
50
|
+
return true;
|
|
51
|
+
// fe80::/10 link-local
|
|
52
|
+
if (lower.startsWith("fe80:"))
|
|
53
|
+
return true;
|
|
54
|
+
// fc00::/7 unique local address (fc00:: and fd00::)
|
|
55
|
+
if (lower.startsWith("fc") || lower.startsWith("fd"))
|
|
56
|
+
return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Public API
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Check whether an IP address falls in a private/reserved range.
|
|
64
|
+
*
|
|
65
|
+
* Covers all RFC 1918 ranges, loopback, link-local, CGNAT, IPv6 ULA,
|
|
66
|
+
* and IPv4-mapped IPv6 addresses.
|
|
67
|
+
*/
|
|
68
|
+
export function isPrivateIp(ip) {
|
|
69
|
+
if (net.isIPv4(ip))
|
|
70
|
+
return isPrivateIPv4(ip);
|
|
71
|
+
if (net.isIPv6(ip))
|
|
72
|
+
return isPrivateIPv6(ip);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Validate that a hostname does not resolve to a private/reserved IP.
|
|
77
|
+
*
|
|
78
|
+
* - Checks cache first (scan-scoped, avoids duplicate lookups)
|
|
79
|
+
* - Skips DNS if hostname is already an IP literal
|
|
80
|
+
* - Resolves ALL addresses and rejects if ANY is private
|
|
81
|
+
* - Caches the first resolved IP on success
|
|
82
|
+
* - Uses a 3-second DNS timeout via AbortSignal
|
|
83
|
+
*/
|
|
84
|
+
export async function validateDns(hostname, cache) {
|
|
85
|
+
// Check cache first
|
|
86
|
+
const cached = cache.get(hostname);
|
|
87
|
+
if (cached !== undefined) {
|
|
88
|
+
if (isPrivateIp(cached)) {
|
|
89
|
+
return {
|
|
90
|
+
safe: false,
|
|
91
|
+
error: `Hostname resolves to private address ${cached}`,
|
|
92
|
+
ip: cached,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { safe: true, ip: cached };
|
|
96
|
+
}
|
|
97
|
+
// If hostname is already an IP literal, skip DNS lookup
|
|
98
|
+
if (net.isIP(hostname) !== 0) {
|
|
99
|
+
if (isPrivateIp(hostname)) {
|
|
100
|
+
return {
|
|
101
|
+
safe: false,
|
|
102
|
+
error: `Hostname resolves to private address ${hostname}`,
|
|
103
|
+
ip: hostname,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { safe: true, ip: hostname };
|
|
107
|
+
}
|
|
108
|
+
// DNS resolution with 3-second timeout
|
|
109
|
+
try {
|
|
110
|
+
const DNS_TIMEOUT_MS = 3000;
|
|
111
|
+
const lookupPromise = dns.lookup(hostname, { all: true });
|
|
112
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("DNS lookup timed out")), DNS_TIMEOUT_MS));
|
|
113
|
+
const records = await Promise.race([lookupPromise, timeoutPromise]);
|
|
114
|
+
// Check ALL resolved addresses -- reject if ANY is private
|
|
115
|
+
for (const record of records) {
|
|
116
|
+
if (isPrivateIp(record.address)) {
|
|
117
|
+
return {
|
|
118
|
+
safe: false,
|
|
119
|
+
error: `Hostname resolves to private address ${record.address}`,
|
|
120
|
+
ip: record.address,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Cache the first resolved IP
|
|
125
|
+
const ip = records[0].address;
|
|
126
|
+
cache.set(hostname, ip);
|
|
127
|
+
return { safe: true, ip };
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
131
|
+
return { safe: false, error: `DNS resolution failed: ${message}` };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=ssrf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrf.js","sourceRoot":"","sources":["../../src/utils/ssrf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Result of successful URL normalization */
|
|
2
|
+
export interface NormalizeUrlOk {
|
|
3
|
+
ok: true;
|
|
4
|
+
/** Full normalized URL (e.g., "https://example.com/path") */
|
|
5
|
+
href: string;
|
|
6
|
+
/** Hostname extracted from URL (e.g., "example.com") */
|
|
7
|
+
domain: string;
|
|
8
|
+
/** Origin: protocol + host (e.g., "https://example.com") */
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
}
|
|
11
|
+
/** Result of failed URL normalization */
|
|
12
|
+
export interface NormalizeUrlErr {
|
|
13
|
+
ok: false;
|
|
14
|
+
error: string;
|
|
15
|
+
}
|
|
16
|
+
/** Discriminated union result -- never throws */
|
|
17
|
+
export type NormalizeUrlResult = NormalizeUrlOk | NormalizeUrlErr;
|
|
18
|
+
/**
|
|
19
|
+
* Normalize a user-provided URL string into a fully-qualified URL.
|
|
20
|
+
*
|
|
21
|
+
* - Trims whitespace
|
|
22
|
+
* - Prepends `https://` if no protocol is present
|
|
23
|
+
* - Strips leading `//` before prepending protocol
|
|
24
|
+
* - Returns a discriminated union -- never throws
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeUrl(input: string): NormalizeUrlResult;
|
|
27
|
+
/**
|
|
28
|
+
* Extract the hostname from a URL string.
|
|
29
|
+
*
|
|
30
|
+
* Assumes the URL is already normalized (has protocol).
|
|
31
|
+
* Throws on invalid URL -- caller must normalize first.
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractDomain(url: string): string;
|
|
34
|
+
/** Result of successful redirect resolution */
|
|
35
|
+
export interface ResolveRedirectOk {
|
|
36
|
+
ok: true;
|
|
37
|
+
url: string;
|
|
38
|
+
}
|
|
39
|
+
/** Result of failed redirect resolution */
|
|
40
|
+
export interface ResolveRedirectErr {
|
|
41
|
+
ok: false;
|
|
42
|
+
error: string;
|
|
43
|
+
}
|
|
44
|
+
/** Discriminated union for redirect resolution */
|
|
45
|
+
export type ResolveRedirectResult = ResolveRedirectOk | ResolveRedirectErr;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a Location header value against the current URL.
|
|
48
|
+
*
|
|
49
|
+
* Handles absolute, relative, and protocol-relative Location values.
|
|
50
|
+
* Returns a discriminated union -- never throws.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveRedirectUrl(locationHeader: string, currentUrl: string): ResolveRedirectResult;
|
|
53
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,IAAI,CAAC;IACT,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,eAAe,CAAC;AAElE;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CA6B9D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;CACb;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,kDAAkD;AAClD,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAE3E;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,qBAAqB,CAavB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a user-provided URL string into a fully-qualified URL.
|
|
3
|
+
*
|
|
4
|
+
* - Trims whitespace
|
|
5
|
+
* - Prepends `https://` if no protocol is present
|
|
6
|
+
* - Strips leading `//` before prepending protocol
|
|
7
|
+
* - Returns a discriminated union -- never throws
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeUrl(input) {
|
|
10
|
+
const trimmed = input.trim();
|
|
11
|
+
if (trimmed === "") {
|
|
12
|
+
return { ok: false, error: "Empty URL" };
|
|
13
|
+
}
|
|
14
|
+
let raw = trimmed;
|
|
15
|
+
// Add protocol if missing
|
|
16
|
+
if (!raw.startsWith("http://") && !raw.startsWith("https://")) {
|
|
17
|
+
// Strip leading // if present (protocol-relative)
|
|
18
|
+
if (raw.startsWith("//")) {
|
|
19
|
+
raw = raw.slice(2);
|
|
20
|
+
}
|
|
21
|
+
raw = "https://" + raw;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(raw);
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
href: url.href,
|
|
28
|
+
domain: url.hostname, // URL constructor lowercases hostname
|
|
29
|
+
baseUrl: url.origin,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { ok: false, error: `Invalid URL: ${trimmed}` };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extract the hostname from a URL string.
|
|
38
|
+
*
|
|
39
|
+
* Assumes the URL is already normalized (has protocol).
|
|
40
|
+
* Throws on invalid URL -- caller must normalize first.
|
|
41
|
+
*/
|
|
42
|
+
export function extractDomain(url) {
|
|
43
|
+
return new URL(url).hostname;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a Location header value against the current URL.
|
|
47
|
+
*
|
|
48
|
+
* Handles absolute, relative, and protocol-relative Location values.
|
|
49
|
+
* Returns a discriminated union -- never throws.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveRedirectUrl(locationHeader, currentUrl) {
|
|
52
|
+
const trimmed = locationHeader.trim();
|
|
53
|
+
if (trimmed === "") {
|
|
54
|
+
return { ok: false, error: "Empty Location header" };
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const resolved = new URL(trimmed, currentUrl).href;
|
|
58
|
+
return { ok: true, url: resolved };
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return { ok: false, error: `Invalid redirect URL: ${trimmed}` };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAoBA;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAE7B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACnB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAC3C,CAAC;IAED,IAAI,GAAG,GAAG,OAAO,CAAC;IAElB,0BAA0B;IAC1B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9D,kDAAkD;QAClD,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;QACD,GAAG,GAAG,UAAU,GAAG,GAAG,CAAC;IACzB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO;YACL,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,GAAG,CAAC,QAAQ,EAAE,sCAAsC;YAC5D,OAAO,EAAE,GAAG,CAAC,MAAM;SACpB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,OAAO,EAAE,EAAE,CAAC;IACzD,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;AAC/B,CAAC;AAiBD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,cAAsB,EACtB,UAAkB;IAElB,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC;IAEtC,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACnB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;IACvD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC;QACnD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,yBAAyB,OAAO,EAAE,EAAE,CAAC;IAClE,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "milieu-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Check if AI agents can discover, understand, and use your API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"milieu": "dist/cli/index.js",
|
|
10
|
+
"milieu-cli": "dist/cli/index.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
23
|
+
"dev": "tsx src/index.ts",
|
|
24
|
+
"test": "vitest run --reporter=verbose",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"prepack": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"license": "Apache-2.0",
|
|
33
|
+
"author": "Arun <arun@simplyarun.com>",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"ai",
|
|
36
|
+
"ai-agents",
|
|
37
|
+
"llm",
|
|
38
|
+
"api",
|
|
39
|
+
"api-readiness",
|
|
40
|
+
"agent-readiness",
|
|
41
|
+
"agentic-web",
|
|
42
|
+
"lighthouse",
|
|
43
|
+
"cli",
|
|
44
|
+
"website-scanner",
|
|
45
|
+
"developer-tools",
|
|
46
|
+
"robots-txt",
|
|
47
|
+
"llms-txt",
|
|
48
|
+
"openapi",
|
|
49
|
+
"mcp",
|
|
50
|
+
"json-ld",
|
|
51
|
+
"schema-org",
|
|
52
|
+
"web-standards",
|
|
53
|
+
"machine-readable"
|
|
54
|
+
],
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/simplyarun/milieu-cli.git"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/simplyarun/milieu-cli#readme",
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/simplyarun/milieu-cli/issues"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^22.0.0",
|
|
65
|
+
"tsx": "^4",
|
|
66
|
+
"typescript": "^5.9",
|
|
67
|
+
"vitest": "^4.1.0"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"chalk": "^5.6.2",
|
|
71
|
+
"commander": "^14.0.3",
|
|
72
|
+
"ora": "^9.3.0"
|
|
73
|
+
}
|
|
74
|
+
}
|