@agentcash/discovery 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,9 +24,16 @@ __export(index_exports, {
24
24
  LEGACY_SUNSET_DATE: () => LEGACY_SUNSET_DATE,
25
25
  STRICT_ESCALATION_CODES: () => STRICT_ESCALATION_CODES,
26
26
  UPGRADE_WARNING_CODES: () => UPGRADE_WARNING_CODES,
27
+ auditContextHarness: () => auditContextHarness,
28
+ buildContextHarnessReport: () => buildContextHarnessReport,
27
29
  computeUpgradeSignal: () => computeUpgradeSignal,
30
+ defaultHarnessProbeCandidates: () => defaultHarnessProbeCandidates,
28
31
  discover: () => discover,
29
- discoverDetailed: () => discoverDetailed
32
+ discoverDetailed: () => discoverDetailed,
33
+ discoverForMcp: () => discoverForMcp,
34
+ getHarnessClientProfile: () => getHarnessClientProfile,
35
+ inspectEndpointForMcp: () => inspectEndpointForMcp,
36
+ listHarnessClientProfiles: () => listHarnessClientProfiles
30
37
  });
31
38
  module.exports = __toCommonJS(index_exports);
32
39
 
@@ -43,6 +50,9 @@ var STRICT_ESCALATION_CODES = [
43
50
  "INTEROP_MPP_USED"
44
51
  ];
45
52
 
53
+ // src/mmm-enabled.ts
54
+ var isMmmEnabled = () => "0.1.2".includes("-mmm");
55
+
46
56
  // src/core/constants.ts
47
57
  var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
48
58
  var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
@@ -995,7 +1005,7 @@ function detectProtocols(response) {
995
1005
  }
996
1006
  const authHeader = response.headers.get("www-authenticate")?.toLowerCase() ?? "";
997
1007
  if (authHeader.includes("x402")) protocols.add("x402");
998
- if (authHeader.includes("mpp")) protocols.add("mpp");
1008
+ if (isMmmEnabled() && authHeader.includes("mpp")) protocols.add("mpp");
999
1009
  return [...protocols];
1000
1010
  }
1001
1011
  async function runProbeStage(options) {
@@ -1327,7 +1337,7 @@ async function runDiscovery({
1327
1337
  includeRaw
1328
1338
  })
1329
1339
  );
1330
- if (includeInteropMpp) {
1340
+ if (includeInteropMpp && isMmmEnabled()) {
1331
1341
  stages.push(
1332
1342
  () => runInteropMppStage({
1333
1343
  origin,
@@ -1429,6 +1439,636 @@ async function runDiscovery({
1429
1439
  };
1430
1440
  }
1431
1441
 
1442
+ // src/adapters/mcp.ts
1443
+ var DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS = 1e3;
1444
+ var OPENAPI_METHODS = [
1445
+ "GET",
1446
+ "POST",
1447
+ "PUT",
1448
+ "DELETE",
1449
+ "PATCH",
1450
+ "HEAD",
1451
+ "OPTIONS",
1452
+ "TRACE"
1453
+ ];
1454
+ function isRecord4(value) {
1455
+ return value != null && typeof value === "object" && !Array.isArray(value);
1456
+ }
1457
+ function toFiniteNumber(value) {
1458
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1459
+ if (typeof value === "string" && value.trim().length > 0) {
1460
+ const parsed = Number(value);
1461
+ if (Number.isFinite(parsed)) return parsed;
1462
+ }
1463
+ return void 0;
1464
+ }
1465
+ function formatPriceHintFromResource(resource) {
1466
+ const pricing = resource.pricing;
1467
+ if (pricing) {
1468
+ if (pricing.pricingMode === "fixed" && pricing.price) {
1469
+ return pricing.price.startsWith("$") ? pricing.price : `$${pricing.price}`;
1470
+ }
1471
+ if (pricing.pricingMode === "range" && pricing.minPrice && pricing.maxPrice) {
1472
+ const min = pricing.minPrice.startsWith("$") ? pricing.minPrice : `$${pricing.minPrice}`;
1473
+ const max = pricing.maxPrice.startsWith("$") ? pricing.maxPrice : `$${pricing.maxPrice}`;
1474
+ return `${min}-${max}`;
1475
+ }
1476
+ }
1477
+ if (!resource.priceHint) return void 0;
1478
+ const hint = resource.priceHint.trim();
1479
+ if (hint.length === 0) return void 0;
1480
+ if (hint.startsWith("$")) return hint;
1481
+ if (hint.includes("-")) {
1482
+ const [min, max] = hint.split("-", 2).map((part) => part.trim());
1483
+ if (min && max) return `$${min}-$${max}`;
1484
+ }
1485
+ return `$${hint}`;
1486
+ }
1487
+ function deriveMcpAuthMode(resource) {
1488
+ if (!resource) return void 0;
1489
+ const hint = resource.authHint;
1490
+ if (hint === "paid" || hint === "siwx" || hint === "apiKey" || hint === "unprotected") {
1491
+ return hint;
1492
+ }
1493
+ return void 0;
1494
+ }
1495
+ function inferFailureCause(warnings) {
1496
+ const openapiWarnings = warnings.filter((w) => w.stage === "openapi");
1497
+ const parseWarning = openapiWarnings.find(
1498
+ (w) => w.code === "PARSE_FAILED" || w.code === "OPENAPI_TOP_LEVEL_INVALID" || w.code === "OPENAPI_OPERATION_INVALID" || w.code === "OPENAPI_PRICING_INVALID"
1499
+ );
1500
+ if (parseWarning) {
1501
+ return { cause: "parse", message: parseWarning.message };
1502
+ }
1503
+ const fetchWarning = openapiWarnings.find((w) => w.code === "FETCH_FAILED");
1504
+ if (fetchWarning) {
1505
+ const msg = fetchWarning.message.toLowerCase();
1506
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("abort")) {
1507
+ return { cause: "timeout", message: fetchWarning.message };
1508
+ }
1509
+ return { cause: "network", message: fetchWarning.message };
1510
+ }
1511
+ return { cause: "not_found" };
1512
+ }
1513
+ function getOpenApiInfo(document) {
1514
+ if (!isRecord4(document) || !isRecord4(document.info)) return void 0;
1515
+ if (typeof document.info.title !== "string") return void 0;
1516
+ return {
1517
+ title: document.info.title,
1518
+ ...typeof document.info.version === "string" ? { version: document.info.version } : {},
1519
+ ...typeof document.info.description === "string" ? { description: document.info.description } : {}
1520
+ };
1521
+ }
1522
+ function getGuidanceUrl(result) {
1523
+ const fromTrace = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1524
+ if (fromTrace) return fromTrace;
1525
+ const fromResource = result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1526
+ if (fromResource) return fromResource;
1527
+ return void 0;
1528
+ }
1529
+ async function resolveGuidance(options) {
1530
+ let guidanceText = typeof options.result.rawSources?.llmsTxt === "string" ? options.result.rawSources.llmsTxt : void 0;
1531
+ if (!guidanceText) {
1532
+ const guidanceUrl = getGuidanceUrl(options.result) ?? `${options.result.origin}/llms.txt`;
1533
+ const fetcher = options.fetcher ?? fetch;
1534
+ try {
1535
+ const response = await fetcher(guidanceUrl, {
1536
+ method: "GET",
1537
+ headers: { Accept: "text/plain", ...options.headers },
1538
+ signal: options.signal
1539
+ });
1540
+ if (response.ok) guidanceText = await response.text();
1541
+ } catch {
1542
+ }
1543
+ }
1544
+ if (!guidanceText) {
1545
+ return { guidanceAvailable: false };
1546
+ }
1547
+ const guidanceTokens = estimateTokenCount(guidanceText);
1548
+ const threshold = options.guidanceAutoIncludeMaxTokens ?? DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS;
1549
+ const shouldInclude = options.includeGuidance === true || options.includeGuidance == null && guidanceTokens <= threshold;
1550
+ return {
1551
+ guidanceAvailable: true,
1552
+ guidanceTokens,
1553
+ ...shouldInclude ? { guidance: guidanceText } : {}
1554
+ };
1555
+ }
1556
+ function extractPathsDocument(document) {
1557
+ if (!isRecord4(document)) return void 0;
1558
+ if (!isRecord4(document.paths)) return void 0;
1559
+ return document.paths;
1560
+ }
1561
+ function findMatchingOpenApiPath(paths, targetPath) {
1562
+ const exact = paths[targetPath];
1563
+ if (isRecord4(exact)) return { matchedPath: targetPath, pathItem: exact };
1564
+ for (const [specPath, entry] of Object.entries(paths)) {
1565
+ if (!isRecord4(entry)) continue;
1566
+ const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
1567
+ const regex = new RegExp(`^${pattern}$`);
1568
+ if (regex.test(targetPath)) {
1569
+ return { matchedPath: specPath, pathItem: entry };
1570
+ }
1571
+ }
1572
+ return null;
1573
+ }
1574
+ function resolveRef(document, ref, seen) {
1575
+ if (!ref.startsWith("#/")) return void 0;
1576
+ if (seen.has(ref)) return { $circular: ref };
1577
+ seen.add(ref);
1578
+ const parts = ref.slice(2).split("/");
1579
+ let current = document;
1580
+ for (const part of parts) {
1581
+ if (!isRecord4(current)) return void 0;
1582
+ current = current[part];
1583
+ if (current === void 0) return void 0;
1584
+ }
1585
+ if (isRecord4(current)) return resolveRefs(document, current, seen);
1586
+ return current;
1587
+ }
1588
+ function resolveRefs(document, obj, seen, depth = 0) {
1589
+ if (depth > 4) return obj;
1590
+ const resolved = {};
1591
+ for (const [key, value] of Object.entries(obj)) {
1592
+ if (key === "$ref" && typeof value === "string") {
1593
+ const deref = resolveRef(document, value, seen);
1594
+ if (isRecord4(deref)) {
1595
+ Object.assign(resolved, deref);
1596
+ } else {
1597
+ resolved[key] = value;
1598
+ }
1599
+ continue;
1600
+ }
1601
+ if (isRecord4(value)) {
1602
+ resolved[key] = resolveRefs(document, value, seen, depth + 1);
1603
+ continue;
1604
+ }
1605
+ if (Array.isArray(value)) {
1606
+ resolved[key] = value.map(
1607
+ (item) => isRecord4(item) ? resolveRefs(document, item, seen, depth + 1) : item
1608
+ );
1609
+ continue;
1610
+ }
1611
+ resolved[key] = value;
1612
+ }
1613
+ return resolved;
1614
+ }
1615
+ function extractRequestBodySchema(operationSchema) {
1616
+ const requestBody = operationSchema.requestBody;
1617
+ if (!isRecord4(requestBody)) return void 0;
1618
+ const content = requestBody.content;
1619
+ if (!isRecord4(content)) return void 0;
1620
+ const jsonMediaType = content["application/json"];
1621
+ if (isRecord4(jsonMediaType) && isRecord4(jsonMediaType.schema)) {
1622
+ return jsonMediaType.schema;
1623
+ }
1624
+ for (const mediaType of Object.values(content)) {
1625
+ if (isRecord4(mediaType) && isRecord4(mediaType.schema)) {
1626
+ return mediaType.schema;
1627
+ }
1628
+ }
1629
+ return void 0;
1630
+ }
1631
+ function extractParameters(operationSchema) {
1632
+ const params = operationSchema.parameters;
1633
+ if (!Array.isArray(params)) return [];
1634
+ return params.filter((value) => isRecord4(value));
1635
+ }
1636
+ function extractInputSchema(operationSchema) {
1637
+ const requestBody = extractRequestBodySchema(operationSchema);
1638
+ const parameters = extractParameters(operationSchema);
1639
+ if (!requestBody && parameters.length === 0) return void 0;
1640
+ if (requestBody && parameters.length === 0) return requestBody;
1641
+ return {
1642
+ ...requestBody ? { requestBody } : {},
1643
+ ...parameters.length > 0 ? { parameters } : {}
1644
+ };
1645
+ }
1646
+ function parseOperationPrice(operation) {
1647
+ const paymentInfo = operation["x-payment-info"];
1648
+ if (!isRecord4(paymentInfo)) return void 0;
1649
+ const fixed = toFiniteNumber(paymentInfo.price);
1650
+ if (fixed != null) return `$${String(fixed)}`;
1651
+ const min = toFiniteNumber(paymentInfo.minPrice);
1652
+ const max = toFiniteNumber(paymentInfo.maxPrice);
1653
+ if (min != null && max != null) return `$${String(min)}-$${String(max)}`;
1654
+ return void 0;
1655
+ }
1656
+ function parseOperationProtocols(operation) {
1657
+ const paymentInfo = operation["x-payment-info"];
1658
+ if (!isRecord4(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
1659
+ const protocols = paymentInfo.protocols.filter(
1660
+ (protocol) => typeof protocol === "string" && protocol.length > 0
1661
+ );
1662
+ return protocols.length > 0 ? protocols : void 0;
1663
+ }
1664
+ function parseOperationAuthMode(operation) {
1665
+ const authExtension = operation["x-agentcash-auth"];
1666
+ if (isRecord4(authExtension)) {
1667
+ const mode = authExtension.mode;
1668
+ if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
1669
+ return mode;
1670
+ }
1671
+ }
1672
+ if (isRecord4(operation["x-payment-info"])) return "paid";
1673
+ return void 0;
1674
+ }
1675
+ function createResourceMap(result) {
1676
+ const map = /* @__PURE__ */ new Map();
1677
+ for (const resource of result.resources) {
1678
+ map.set(resource.resourceKey, resource);
1679
+ }
1680
+ return map;
1681
+ }
1682
+ function toMcpEndpointSummary(resource) {
1683
+ const method = parseMethod(resource.method);
1684
+ if (!method) return void 0;
1685
+ const price = formatPriceHintFromResource(resource);
1686
+ const authMode = deriveMcpAuthMode(resource);
1687
+ return {
1688
+ path: resource.path,
1689
+ method,
1690
+ summary: resource.summary ?? `${method} ${resource.path}`,
1691
+ ...price ? { price } : {},
1692
+ ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
1693
+ ...authMode ? { authMode } : {}
1694
+ };
1695
+ }
1696
+ async function discoverForMcp(options) {
1697
+ const result = await runDiscovery({
1698
+ detailed: true,
1699
+ options: {
1700
+ target: options.target,
1701
+ compatMode: options.compatMode ?? "off",
1702
+ rawView: "full",
1703
+ fetcher: options.fetcher,
1704
+ headers: options.headers,
1705
+ signal: options.signal
1706
+ }
1707
+ });
1708
+ if (result.resources.length === 0) {
1709
+ const failure = inferFailureCause(result.warnings);
1710
+ return {
1711
+ found: false,
1712
+ origin: result.origin,
1713
+ cause: failure.cause,
1714
+ ...failure.message ? { message: failure.message } : {},
1715
+ warnings: result.warnings
1716
+ };
1717
+ }
1718
+ const source = result.selectedStage ?? "openapi";
1719
+ const endpoints = result.resources.map(toMcpEndpointSummary).filter((endpoint) => endpoint != null).sort((a, b) => {
1720
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
1721
+ return a.method.localeCompare(b.method);
1722
+ });
1723
+ const guidance = await resolveGuidance({
1724
+ result,
1725
+ includeGuidance: options.includeGuidance,
1726
+ guidanceAutoIncludeMaxTokens: options.guidanceAutoIncludeMaxTokens,
1727
+ fetcher: options.fetcher,
1728
+ headers: options.headers,
1729
+ signal: options.signal
1730
+ });
1731
+ return {
1732
+ found: true,
1733
+ origin: result.origin,
1734
+ source,
1735
+ ...getOpenApiInfo(result.rawSources?.openapi) ? { info: getOpenApiInfo(result.rawSources?.openapi) } : {},
1736
+ ...guidance,
1737
+ endpoints,
1738
+ warnings: result.warnings
1739
+ };
1740
+ }
1741
+ async function inspectEndpointForMcp(options) {
1742
+ const endpoint = new URL(options.endpointUrl);
1743
+ const origin = normalizeOrigin(endpoint.origin);
1744
+ const path = normalizePath(endpoint.pathname || "/");
1745
+ const result = await runDiscovery({
1746
+ detailed: true,
1747
+ options: {
1748
+ target: options.target,
1749
+ compatMode: options.compatMode ?? "off",
1750
+ rawView: "full",
1751
+ fetcher: options.fetcher,
1752
+ headers: options.headers,
1753
+ signal: options.signal
1754
+ }
1755
+ });
1756
+ const document = result.rawSources?.openapi;
1757
+ const paths = extractPathsDocument(document);
1758
+ const resourceMap = createResourceMap(result);
1759
+ const advisories = {};
1760
+ const specMethods = [];
1761
+ if (paths) {
1762
+ const matched = findMatchingOpenApiPath(paths, path);
1763
+ if (matched) {
1764
+ for (const candidate of OPENAPI_METHODS) {
1765
+ const operation = matched.pathItem[candidate.toLowerCase()];
1766
+ if (!isRecord4(operation) || !isRecord4(document)) continue;
1767
+ specMethods.push(candidate);
1768
+ const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
1769
+ const resource = resourceMap.get(toResourceKey(origin, candidate, matched.matchedPath));
1770
+ const formattedPrice = resource ? formatPriceHintFromResource(resource) : void 0;
1771
+ const operationSummary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
1772
+ const operationPrice = parseOperationPrice(resolvedOperation);
1773
+ const operationProtocols = parseOperationProtocols(resolvedOperation);
1774
+ const operationAuthMode = parseOperationAuthMode(resolvedOperation);
1775
+ const inputSchema = extractInputSchema(resolvedOperation);
1776
+ advisories[candidate] = {
1777
+ method: candidate,
1778
+ ...resource?.summary || operationSummary ? {
1779
+ summary: resource?.summary ?? operationSummary
1780
+ } : {},
1781
+ ...formattedPrice || operationPrice ? {
1782
+ estimatedPrice: formattedPrice ?? operationPrice
1783
+ } : {},
1784
+ ...resource?.protocolHints?.length || operationProtocols ? {
1785
+ protocols: resource?.protocolHints ?? operationProtocols
1786
+ } : {},
1787
+ ...deriveMcpAuthMode(resource) || operationAuthMode ? {
1788
+ authMode: deriveMcpAuthMode(resource) ?? operationAuthMode
1789
+ } : {},
1790
+ ...inputSchema ? { inputSchema } : {},
1791
+ operationSchema: resolvedOperation
1792
+ };
1793
+ }
1794
+ }
1795
+ }
1796
+ if (specMethods.length === 0) {
1797
+ for (const method of OPENAPI_METHODS) {
1798
+ const resource = resourceMap.get(toResourceKey(origin, method, path));
1799
+ if (!resource) continue;
1800
+ specMethods.push(method);
1801
+ const formattedPrice = formatPriceHintFromResource(resource);
1802
+ advisories[method] = {
1803
+ method,
1804
+ ...resource.summary ? { summary: resource.summary } : {},
1805
+ ...formattedPrice ? { estimatedPrice: formattedPrice } : {},
1806
+ ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
1807
+ ...deriveMcpAuthMode(resource) ? { authMode: deriveMcpAuthMode(resource) } : {}
1808
+ };
1809
+ }
1810
+ }
1811
+ if (specMethods.length === 0) {
1812
+ const failure = inferFailureCause(result.warnings);
1813
+ return {
1814
+ found: false,
1815
+ origin,
1816
+ path,
1817
+ cause: failure.cause,
1818
+ ...failure.message ? { message: failure.message } : {},
1819
+ warnings: result.warnings
1820
+ };
1821
+ }
1822
+ return {
1823
+ found: true,
1824
+ origin,
1825
+ path,
1826
+ specMethods,
1827
+ advisories,
1828
+ warnings: result.warnings
1829
+ };
1830
+ }
1831
+
1832
+ // src/harness.ts
1833
+ var INTENT_TRIGGERS = ["x402", "mpp", "pay for", "micropayment", "agentic commerce"];
1834
+ var CLIENT_PROFILES = {
1835
+ "claude-code": {
1836
+ id: "claude-code",
1837
+ label: "Claude Code MCP Harness",
1838
+ surface: "mcp",
1839
+ defaultContextWindowTokens: 2e5,
1840
+ zeroHopBudgetPercent: 0.1,
1841
+ notes: "Targets environments where MCP context can use roughly 10% of total context."
1842
+ },
1843
+ "skill-cli": {
1844
+ id: "skill-cli",
1845
+ label: "Skill + CLI Harness",
1846
+ surface: "skill-cli",
1847
+ defaultContextWindowTokens: 2e5,
1848
+ zeroHopBudgetPercent: 0.02,
1849
+ notes: "Targets constrained skill contexts with title/description-heavy routing."
1850
+ },
1851
+ "generic-mcp": {
1852
+ id: "generic-mcp",
1853
+ label: "Generic MCP Harness",
1854
+ surface: "mcp",
1855
+ defaultContextWindowTokens: 128e3,
1856
+ zeroHopBudgetPercent: 0.05,
1857
+ notes: "Conservative MCP profile when specific client budgets are unknown."
1858
+ },
1859
+ generic: {
1860
+ id: "generic",
1861
+ label: "Generic Agent Harness",
1862
+ surface: "generic",
1863
+ defaultContextWindowTokens: 128e3,
1864
+ zeroHopBudgetPercent: 0.03,
1865
+ notes: "Fallback profile for unknown clients."
1866
+ }
1867
+ };
1868
+ function isFirstPartyDomain(hostname) {
1869
+ const normalized = hostname.toLowerCase();
1870
+ return normalized.endsWith(".dev") && normalized.startsWith("stable");
1871
+ }
1872
+ function safeHostname(origin) {
1873
+ try {
1874
+ return new URL(origin).hostname;
1875
+ } catch {
1876
+ return origin.replace(/^https?:\/\//, "").split("/")[0] ?? origin;
1877
+ }
1878
+ }
1879
+ function previewText(text) {
1880
+ if (!text) return null;
1881
+ const compact = text.replace(/\s+/g, " ").trim();
1882
+ if (!compact) return null;
1883
+ return compact.length > 220 ? `${compact.slice(0, 217)}...` : compact;
1884
+ }
1885
+ function toAuthModeCount(resources) {
1886
+ const counts = {
1887
+ paid: 0,
1888
+ siwx: 0,
1889
+ apiKey: 0,
1890
+ unprotected: 0,
1891
+ unknown: 0
1892
+ };
1893
+ for (const resource of resources) {
1894
+ const mode = resource.authHint;
1895
+ if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
1896
+ counts[mode] += 1;
1897
+ } else {
1898
+ counts.unknown += 1;
1899
+ }
1900
+ }
1901
+ return counts;
1902
+ }
1903
+ function toSourceCount(resources) {
1904
+ return resources.reduce(
1905
+ (acc, resource) => {
1906
+ acc[resource.source] = (acc[resource.source] ?? 0) + 1;
1907
+ return acc;
1908
+ },
1909
+ {}
1910
+ );
1911
+ }
1912
+ function toL2Entries(resources) {
1913
+ return [...resources].sort((a, b) => {
1914
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
1915
+ if (a.method !== b.method) return a.method.localeCompare(b.method);
1916
+ return a.resourceKey.localeCompare(b.resourceKey);
1917
+ }).map((resource) => ({
1918
+ resourceKey: resource.resourceKey,
1919
+ method: resource.method,
1920
+ path: resource.path,
1921
+ source: resource.source,
1922
+ authMode: resource.authHint ?? null
1923
+ }));
1924
+ }
1925
+ function getLlmsTxtInfo(result) {
1926
+ const llmsTxtUrl = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? null;
1927
+ const llmsTxt = result.rawSources?.llmsTxt;
1928
+ if (llmsTxt) {
1929
+ return {
1930
+ llmsTxtUrl,
1931
+ llmsTxtTokenEstimate: estimateTokenCount(llmsTxt),
1932
+ guidancePreview: previewText(llmsTxt),
1933
+ guidanceStatus: "present"
1934
+ };
1935
+ }
1936
+ if (llmsTxtUrl) {
1937
+ const failed = result.warnings.some((warning2) => warning2.code === "LLMSTXT_FETCH_FAILED");
1938
+ return {
1939
+ llmsTxtUrl,
1940
+ llmsTxtTokenEstimate: 0,
1941
+ guidancePreview: null,
1942
+ guidanceStatus: failed ? "advertised_but_unfetched" : "missing"
1943
+ };
1944
+ }
1945
+ return {
1946
+ llmsTxtUrl: null,
1947
+ llmsTxtTokenEstimate: 0,
1948
+ guidancePreview: null,
1949
+ guidanceStatus: "not_advertised"
1950
+ };
1951
+ }
1952
+ function getHarnessClientProfile(client = "claude-code") {
1953
+ return CLIENT_PROFILES[client] ?? CLIENT_PROFILES.generic;
1954
+ }
1955
+ function listHarnessClientProfiles() {
1956
+ return Object.values(CLIENT_PROFILES);
1957
+ }
1958
+ function buildContextHarnessReport(options) {
1959
+ const client = getHarnessClientProfile(options.client);
1960
+ const contextWindowTokens = options.contextWindowTokens ?? client.defaultContextWindowTokens;
1961
+ const zeroHopBudgetTokens = Math.floor(contextWindowTokens * client.zeroHopBudgetPercent);
1962
+ const hostname = safeHostname(options.result.origin);
1963
+ const domainClass = isFirstPartyDomain(hostname) ? "first-party" : "ugc";
1964
+ const l0Summary = "Route payment-capable intents to agentcash and use install/discover commands for progressive disclosure.";
1965
+ const l1Summary = "Expose installed domain routing hints and trigger fan-out into L2/L3 commands when user intent matches domain capabilities.";
1966
+ const l0EstimatedTokens = estimateTokenCount(
1967
+ `${INTENT_TRIGGERS.join(", ")} npx agentcash install npx agentcash install-ext`
1968
+ );
1969
+ const l1EstimatedTokens = estimateTokenCount(
1970
+ `${hostname} ${l1Summary} npx agentcash discover ${hostname} npx agentcash discover ${hostname} --verbose`
1971
+ );
1972
+ const llmsInfo = getLlmsTxtInfo(options.result);
1973
+ return {
1974
+ target: options.target,
1975
+ origin: options.result.origin,
1976
+ client,
1977
+ budget: {
1978
+ contextWindowTokens,
1979
+ zeroHopBudgetTokens,
1980
+ estimatedZeroHopTokens: l0EstimatedTokens + l1EstimatedTokens,
1981
+ withinBudget: l0EstimatedTokens + l1EstimatedTokens <= zeroHopBudgetTokens
1982
+ },
1983
+ levels: {
1984
+ l0: {
1985
+ layer: "L0",
1986
+ intentTriggers: INTENT_TRIGGERS,
1987
+ installCommand: "npx agentcash install",
1988
+ deliverySurfaces: ["MCP", "Skill+CLI"],
1989
+ summary: l0Summary,
1990
+ estimatedTokens: l0EstimatedTokens
1991
+ },
1992
+ l1: {
1993
+ layer: "L1",
1994
+ domain: hostname,
1995
+ domainClass,
1996
+ selectedDiscoveryStage: options.result.selectedStage ?? null,
1997
+ summary: l1Summary,
1998
+ fanoutCommands: [
1999
+ `npx agentcash discover ${hostname}`,
2000
+ `npx agentcash discover ${hostname} --verbose`
2001
+ ],
2002
+ estimatedTokens: l1EstimatedTokens
2003
+ },
2004
+ l2: {
2005
+ layer: "L2",
2006
+ command: `npx agentcash discover ${hostname}`,
2007
+ tokenLight: true,
2008
+ resourceCount: options.result.resources.length,
2009
+ resources: toL2Entries(options.result.resources)
2010
+ },
2011
+ l3: {
2012
+ layer: "L3",
2013
+ command: `npx agentcash discover ${hostname} --verbose`,
2014
+ detailLevel: "endpoint-schema-and-metadata",
2015
+ countsByAuthMode: toAuthModeCount(options.result.resources),
2016
+ countsBySource: toSourceCount(options.result.resources),
2017
+ pricedResourceCount: options.result.resources.filter(
2018
+ (resource) => resource.authHint === "paid"
2019
+ ).length
2020
+ },
2021
+ l4: {
2022
+ layer: "L4",
2023
+ guidanceSource: llmsInfo.llmsTxtUrl ? "llms.txt" : "none",
2024
+ llmsTxtUrl: llmsInfo.llmsTxtUrl,
2025
+ llmsTxtTokenEstimate: llmsInfo.llmsTxtTokenEstimate,
2026
+ guidancePreview: llmsInfo.guidancePreview,
2027
+ guidanceStatus: llmsInfo.guidanceStatus
2028
+ },
2029
+ l5: {
2030
+ layer: "L5",
2031
+ status: "out_of_scope",
2032
+ note: "Cross-domain composition is intentionally out of scope for discovery v1."
2033
+ }
2034
+ },
2035
+ diagnostics: {
2036
+ warningCount: options.result.warnings.length,
2037
+ errorWarningCount: options.result.warnings.filter((warning2) => warning2.severity === "error").length,
2038
+ selectedStage: options.result.selectedStage ?? null,
2039
+ upgradeSuggested: options.result.upgradeSuggested,
2040
+ upgradeReasons: options.result.upgradeReasons
2041
+ }
2042
+ };
2043
+ }
2044
+ async function auditContextHarness(options) {
2045
+ const result = await runDiscovery({
2046
+ detailed: true,
2047
+ options: {
2048
+ ...options,
2049
+ rawView: "full"
2050
+ }
2051
+ });
2052
+ return buildContextHarnessReport({
2053
+ target: options.target,
2054
+ result,
2055
+ client: options.client,
2056
+ contextWindowTokens: options.contextWindowTokens
2057
+ });
2058
+ }
2059
+ function defaultHarnessProbeCandidates(report) {
2060
+ const methodsByPath = /* @__PURE__ */ new Map();
2061
+ for (const resource of report.levels.l2.resources) {
2062
+ const methods = methodsByPath.get(resource.path) ?? /* @__PURE__ */ new Set();
2063
+ methods.add(resource.method);
2064
+ methodsByPath.set(resource.path, methods);
2065
+ }
2066
+ return [...methodsByPath.entries()].map(([path, methods]) => ({
2067
+ path,
2068
+ methods: [...methods]
2069
+ }));
2070
+ }
2071
+
1432
2072
  // src/index.ts
1433
2073
  async function discover(options) {
1434
2074
  return await runDiscovery({ detailed: false, options });
@@ -1442,7 +2082,14 @@ async function discoverDetailed(options) {
1442
2082
  LEGACY_SUNSET_DATE,
1443
2083
  STRICT_ESCALATION_CODES,
1444
2084
  UPGRADE_WARNING_CODES,
2085
+ auditContextHarness,
2086
+ buildContextHarnessReport,
1445
2087
  computeUpgradeSignal,
2088
+ defaultHarnessProbeCandidates,
1446
2089
  discover,
1447
- discoverDetailed
2090
+ discoverDetailed,
2091
+ discoverForMcp,
2092
+ getHarnessClientProfile,
2093
+ inspectEndpointForMcp,
2094
+ listHarnessClientProfiles
1448
2095
  });