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.
Files changed (159) hide show
  1. package/LICENSE +200 -0
  2. package/README.md +153 -0
  3. package/dist/bridges/index.d.ts +5 -0
  4. package/dist/bridges/index.d.ts.map +1 -0
  5. package/dist/bridges/index.js +6 -0
  6. package/dist/bridges/index.js.map +1 -0
  7. package/dist/bridges/reachability/crawler-policy.d.ts +36 -0
  8. package/dist/bridges/reachability/crawler-policy.d.ts.map +1 -0
  9. package/dist/bridges/reachability/crawler-policy.js +110 -0
  10. package/dist/bridges/reachability/crawler-policy.js.map +1 -0
  11. package/dist/bridges/reachability/http-status.d.ts +7 -0
  12. package/dist/bridges/reachability/http-status.d.ts.map +1 -0
  13. package/dist/bridges/reachability/http-status.js +74 -0
  14. package/dist/bridges/reachability/http-status.js.map +1 -0
  15. package/dist/bridges/reachability/https-check.d.ts +14 -0
  16. package/dist/bridges/reachability/https-check.d.ts.map +1 -0
  17. package/dist/bridges/reachability/https-check.js +38 -0
  18. package/dist/bridges/reachability/https-check.js.map +1 -0
  19. package/dist/bridges/reachability/index.d.ts +13 -0
  20. package/dist/bridges/reachability/index.d.ts.map +1 -0
  21. package/dist/bridges/reachability/index.js +115 -0
  22. package/dist/bridges/reachability/index.js.map +1 -0
  23. package/dist/bridges/reachability/meta-robots.d.ts +16 -0
  24. package/dist/bridges/reachability/meta-robots.d.ts.map +1 -0
  25. package/dist/bridges/reachability/meta-robots.js +119 -0
  26. package/dist/bridges/reachability/meta-robots.js.map +1 -0
  27. package/dist/bridges/reachability/robots-parser.d.ts +26 -0
  28. package/dist/bridges/reachability/robots-parser.d.ts.map +1 -0
  29. package/dist/bridges/reachability/robots-parser.js +105 -0
  30. package/dist/bridges/reachability/robots-parser.js.map +1 -0
  31. package/dist/bridges/reachability/robots-txt.d.ts +14 -0
  32. package/dist/bridges/reachability/robots-txt.d.ts.map +1 -0
  33. package/dist/bridges/reachability/robots-txt.js +80 -0
  34. package/dist/bridges/reachability/robots-txt.js.map +1 -0
  35. package/dist/bridges/separation/api-presence.d.ts +14 -0
  36. package/dist/bridges/separation/api-presence.d.ts.map +1 -0
  37. package/dist/bridges/separation/api-presence.js +96 -0
  38. package/dist/bridges/separation/api-presence.js.map +1 -0
  39. package/dist/bridges/separation/developer-docs.d.ts +21 -0
  40. package/dist/bridges/separation/developer-docs.d.ts.map +1 -0
  41. package/dist/bridges/separation/developer-docs.js +81 -0
  42. package/dist/bridges/separation/developer-docs.js.map +1 -0
  43. package/dist/bridges/separation/index.d.ts +20 -0
  44. package/dist/bridges/separation/index.d.ts.map +1 -0
  45. package/dist/bridges/separation/index.js +63 -0
  46. package/dist/bridges/separation/index.js.map +1 -0
  47. package/dist/bridges/separation/sdk-references.d.ts +12 -0
  48. package/dist/bridges/separation/sdk-references.d.ts.map +1 -0
  49. package/dist/bridges/separation/sdk-references.js +93 -0
  50. package/dist/bridges/separation/sdk-references.js.map +1 -0
  51. package/dist/bridges/separation/webhook-support.d.ts +19 -0
  52. package/dist/bridges/separation/webhook-support.d.ts.map +1 -0
  53. package/dist/bridges/separation/webhook-support.js +94 -0
  54. package/dist/bridges/separation/webhook-support.js.map +1 -0
  55. package/dist/bridges/standards/index.d.ts +13 -0
  56. package/dist/bridges/standards/index.d.ts.map +1 -0
  57. package/dist/bridges/standards/index.js +79 -0
  58. package/dist/bridges/standards/index.js.map +1 -0
  59. package/dist/bridges/standards/json-ld.d.ts +16 -0
  60. package/dist/bridges/standards/json-ld.d.ts.map +1 -0
  61. package/dist/bridges/standards/json-ld.js +63 -0
  62. package/dist/bridges/standards/json-ld.js.map +1 -0
  63. package/dist/bridges/standards/llms-txt.d.ts +19 -0
  64. package/dist/bridges/standards/llms-txt.d.ts.map +1 -0
  65. package/dist/bridges/standards/llms-txt.js +64 -0
  66. package/dist/bridges/standards/llms-txt.js.map +1 -0
  67. package/dist/bridges/standards/mcp.d.ts +13 -0
  68. package/dist/bridges/standards/mcp.d.ts.map +1 -0
  69. package/dist/bridges/standards/mcp.js +72 -0
  70. package/dist/bridges/standards/mcp.js.map +1 -0
  71. package/dist/bridges/standards/openapi.d.ts +14 -0
  72. package/dist/bridges/standards/openapi.d.ts.map +1 -0
  73. package/dist/bridges/standards/openapi.js +424 -0
  74. package/dist/bridges/standards/openapi.js.map +1 -0
  75. package/dist/bridges/standards/schema-org.d.ts +12 -0
  76. package/dist/bridges/standards/schema-org.d.ts.map +1 -0
  77. package/dist/bridges/standards/schema-org.js +101 -0
  78. package/dist/bridges/standards/schema-org.js.map +1 -0
  79. package/dist/bridges/standards/well-known.d.ts +16 -0
  80. package/dist/bridges/standards/well-known.d.ts.map +1 -0
  81. package/dist/bridges/standards/well-known.js +77 -0
  82. package/dist/bridges/standards/well-known.js.map +1 -0
  83. package/dist/bridges/stubs.d.ts +4 -0
  84. package/dist/bridges/stubs.d.ts.map +1 -0
  85. package/dist/bridges/stubs.js +25 -0
  86. package/dist/bridges/stubs.js.map +1 -0
  87. package/dist/cli/index.d.ts +4 -0
  88. package/dist/cli/index.d.ts.map +1 -0
  89. package/dist/cli/index.js +83 -0
  90. package/dist/cli/index.js.map +1 -0
  91. package/dist/core/explanations.d.ts +11 -0
  92. package/dist/core/explanations.d.ts.map +1 -0
  93. package/dist/core/explanations.js +128 -0
  94. package/dist/core/explanations.js.map +1 -0
  95. package/dist/core/index.d.ts +6 -0
  96. package/dist/core/index.d.ts.map +1 -0
  97. package/dist/core/index.js +6 -0
  98. package/dist/core/index.js.map +1 -0
  99. package/dist/core/scan.d.ts +3 -0
  100. package/dist/core/scan.d.ts.map +1 -0
  101. package/dist/core/scan.js +89 -0
  102. package/dist/core/scan.js.map +1 -0
  103. package/dist/core/types.d.ts +119 -0
  104. package/dist/core/types.d.ts.map +1 -0
  105. package/dist/core/types.js +3 -0
  106. package/dist/core/types.js.map +1 -0
  107. package/dist/core/version.d.ts +2 -0
  108. package/dist/core/version.d.ts.map +1 -0
  109. package/dist/core/version.js +7 -0
  110. package/dist/core/version.js.map +1 -0
  111. package/dist/index.d.ts +2 -0
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.js +4 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/render/colors.d.ts +7 -0
  116. package/dist/render/colors.d.ts.map +1 -0
  117. package/dist/render/colors.js +28 -0
  118. package/dist/render/colors.js.map +1 -0
  119. package/dist/render/format-bridge.d.ts +3 -0
  120. package/dist/render/format-bridge.d.ts.map +1 -0
  121. package/dist/render/format-bridge.js +39 -0
  122. package/dist/render/format-bridge.js.map +1 -0
  123. package/dist/render/format-scan.d.ts +3 -0
  124. package/dist/render/format-scan.d.ts.map +1 -0
  125. package/dist/render/format-scan.js +44 -0
  126. package/dist/render/format-scan.js.map +1 -0
  127. package/dist/render/format-verbose.d.ts +3 -0
  128. package/dist/render/format-verbose.d.ts.map +1 -0
  129. package/dist/render/format-verbose.js +14 -0
  130. package/dist/render/format-verbose.js.map +1 -0
  131. package/dist/render/index.d.ts +7 -0
  132. package/dist/render/index.d.ts.map +1 -0
  133. package/dist/render/index.js +8 -0
  134. package/dist/render/index.js.map +1 -0
  135. package/dist/render/progress-bar.d.ts +10 -0
  136. package/dist/render/progress-bar.d.ts.map +1 -0
  137. package/dist/render/progress-bar.js +21 -0
  138. package/dist/render/progress-bar.js.map +1 -0
  139. package/dist/render/symbols.d.ts +10 -0
  140. package/dist/render/symbols.d.ts.map +1 -0
  141. package/dist/render/symbols.js +21 -0
  142. package/dist/render/symbols.js.map +1 -0
  143. package/dist/utils/http-client.d.ts +25 -0
  144. package/dist/utils/http-client.d.ts.map +1 -0
  145. package/dist/utils/http-client.js +235 -0
  146. package/dist/utils/http-client.js.map +1 -0
  147. package/dist/utils/index.d.ts +6 -0
  148. package/dist/utils/index.d.ts.map +1 -0
  149. package/dist/utils/index.js +7 -0
  150. package/dist/utils/index.js.map +1 -0
  151. package/dist/utils/ssrf.d.ts +29 -0
  152. package/dist/utils/ssrf.d.ts.map +1 -0
  153. package/dist/utils/ssrf.js +134 -0
  154. package/dist/utils/ssrf.js.map +1 -0
  155. package/dist/utils/url.d.ts +53 -0
  156. package/dist/utils/url.d.ts.map +1 -0
  157. package/dist/utils/url.js +64 -0
  158. package/dist/utils/url.js.map +1 -0
  159. 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
+ }