@agentcash/discovery 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/dist/cli.js ADDED
@@ -0,0 +1,1798 @@
1
+ // src/flags.ts
2
+ var DEFAULT_COMPAT_MODE = "on";
3
+ var STRICT_ESCALATION_CODES = [
4
+ "LEGACY_WELL_KNOWN_USED",
5
+ "LEGACY_DNS_USED",
6
+ "LEGACY_DNS_PLAIN_URL",
7
+ "LEGACY_MISSING_METHOD",
8
+ "LEGACY_INSTRUCTIONS_USED",
9
+ "LEGACY_OWNERSHIP_PROOFS_USED",
10
+ "INTEROP_MPP_USED"
11
+ ];
12
+
13
+ // src/cli/args.ts
14
+ var DEFAULT_TIMEOUT_MS = 5e3;
15
+ function parseCompatibilityMode(value) {
16
+ if (value === "on" || value === "off" || value === "strict") {
17
+ return value;
18
+ }
19
+ return null;
20
+ }
21
+ function parseTimeoutMs(value) {
22
+ const parsed = Number(value);
23
+ if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
24
+ return null;
25
+ }
26
+ return parsed;
27
+ }
28
+ function parseCliArgs(argv) {
29
+ let target;
30
+ let verbose = false;
31
+ let json = false;
32
+ let probe = false;
33
+ let timeoutMs = DEFAULT_TIMEOUT_MS;
34
+ let compatMode = DEFAULT_COMPAT_MODE;
35
+ let color = true;
36
+ for (let i = 0; i < argv.length; i += 1) {
37
+ const arg = argv[i];
38
+ if (arg === "--help" || arg === "-h") {
39
+ return { kind: "help" };
40
+ }
41
+ if (arg === "--verbose" || arg === "-v") {
42
+ verbose = true;
43
+ continue;
44
+ }
45
+ if (arg === "--json") {
46
+ json = true;
47
+ continue;
48
+ }
49
+ if (arg === "--probe") {
50
+ probe = true;
51
+ continue;
52
+ }
53
+ if (arg === "--no-color") {
54
+ color = false;
55
+ continue;
56
+ }
57
+ if (arg === "--compat") {
58
+ const value = argv[i + 1];
59
+ if (!value) {
60
+ return { kind: "error", message: "Missing value for --compat." };
61
+ }
62
+ const parsed = parseCompatibilityMode(value);
63
+ if (!parsed) {
64
+ return {
65
+ kind: "error",
66
+ message: `Invalid --compat value '${value}'. Expected: on | off | strict.`
67
+ };
68
+ }
69
+ compatMode = parsed;
70
+ i += 1;
71
+ continue;
72
+ }
73
+ if (arg === "--timeout-ms") {
74
+ const value = argv[i + 1];
75
+ if (!value) {
76
+ return { kind: "error", message: "Missing value for --timeout-ms." };
77
+ }
78
+ const parsed = parseTimeoutMs(value);
79
+ if (!parsed) {
80
+ return {
81
+ kind: "error",
82
+ message: `Invalid --timeout-ms value '${value}'. Expected a positive integer.`
83
+ };
84
+ }
85
+ timeoutMs = parsed;
86
+ i += 1;
87
+ continue;
88
+ }
89
+ if (arg.startsWith("-")) {
90
+ return { kind: "error", message: `Unknown flag '${arg}'.` };
91
+ }
92
+ if (target) {
93
+ return {
94
+ kind: "error",
95
+ message: `Unexpected extra positional argument '${arg}'. Only one domain/URL is supported.`
96
+ };
97
+ }
98
+ target = arg;
99
+ }
100
+ if (!target) {
101
+ return { kind: "error", message: "Missing required <domain> argument." };
102
+ }
103
+ return {
104
+ kind: "ok",
105
+ options: {
106
+ target,
107
+ verbose,
108
+ json,
109
+ compatMode,
110
+ probe,
111
+ timeoutMs,
112
+ color
113
+ }
114
+ };
115
+ }
116
+ function renderHelp() {
117
+ return [
118
+ "Usage: npx @agentcash/discovery <domain-or-url> [options]",
119
+ "",
120
+ "Options:",
121
+ " -v, --verbose Show full compatibility matrix",
122
+ " --json Emit JSON output",
123
+ " --compat <mode> Compatibility mode: on | off | strict (default: on)",
124
+ " --probe Re-run with probe candidates from discovered paths",
125
+ " --timeout-ms <ms> Per-request timeout in milliseconds (default: 5000)",
126
+ " --no-color Disable ANSI colors",
127
+ " -h, --help Show help"
128
+ ].join("\n");
129
+ }
130
+
131
+ // src/cli/render.ts
132
+ function createPalette(enabled) {
133
+ const wrap = (code) => (value) => enabled ? `\x1B[${code}m${value}\x1B[0m` : value;
134
+ return {
135
+ dim: wrap(2),
136
+ bold: wrap(1),
137
+ red: wrap(31),
138
+ green: wrap(32),
139
+ yellow: wrap(33),
140
+ cyan: wrap(36)
141
+ };
142
+ }
143
+ function summarizeWarnings(warnings) {
144
+ const errors = warnings.filter((entry) => entry.severity === "error").length;
145
+ const warns = warnings.filter((entry) => entry.severity === "warn").length;
146
+ const infos = warnings.filter((entry) => entry.severity === "info").length;
147
+ const codeCounts = warnings.filter((entry) => entry.code !== "PROBE_STAGE_SKIPPED").reduce(
148
+ (acc, entry) => {
149
+ acc[entry.code] = (acc[entry.code] ?? 0) + 1;
150
+ return acc;
151
+ },
152
+ {}
153
+ );
154
+ const codes = Object.entries(codeCounts).sort((a, b) => b[1] - a[1]).slice(0, 8).map(([code]) => code);
155
+ return {
156
+ total: warnings.length,
157
+ errors,
158
+ warns,
159
+ infos,
160
+ codes
161
+ };
162
+ }
163
+ function formatPricing(resource) {
164
+ if (resource.authHint !== "paid") return "-";
165
+ if (resource.pricing) {
166
+ if (resource.pricing.pricingMode === "fixed") {
167
+ return `fixed:${resource.pricing.price ?? "?"}`;
168
+ }
169
+ if (resource.pricing.pricingMode === "range") {
170
+ return `range:${resource.pricing.minPrice ?? "?"}-${resource.pricing.maxPrice ?? "?"}`;
171
+ }
172
+ return "quote";
173
+ }
174
+ return resource.priceHint ?? "-";
175
+ }
176
+ function asYesNo(value) {
177
+ return value ? "yes" : "no";
178
+ }
179
+ function createTable(headers, rows) {
180
+ const widths = headers.map((header, idx) => {
181
+ const cellWidths = rows.map((row) => row[idx]?.length ?? 0);
182
+ return Math.max(header.length, ...cellWidths);
183
+ });
184
+ const line = (row) => row.map((cell, idx) => {
185
+ const value = cell ?? "";
186
+ return value.padEnd(widths[idx], " ");
187
+ }).join(" | ");
188
+ const separator = widths.map((width) => "-".repeat(width)).join("-|-");
189
+ return [line(headers), separator, ...rows.map((row) => line(row))].join("\n");
190
+ }
191
+ function stageRows(result) {
192
+ return result.trace.map((entry) => {
193
+ const uniqueWarningCodes = [...new Set(entry.warnings.map((warning2) => warning2.code))];
194
+ return [
195
+ entry.stage,
196
+ asYesNo(entry.valid),
197
+ String(entry.resourceCount),
198
+ `${entry.durationMs}ms`,
199
+ uniqueWarningCodes.length > 0 ? uniqueWarningCodes.join(",") : "-",
200
+ result.selectedStage === entry.stage ? "yes" : "-"
201
+ ];
202
+ });
203
+ }
204
+ function resourceRows(result) {
205
+ return [...result.resources].sort((a, b) => {
206
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
207
+ if (a.method !== b.method) return a.method.localeCompare(b.method);
208
+ return a.origin.localeCompare(b.origin);
209
+ }).map((resource) => [
210
+ resource.method,
211
+ resource.path,
212
+ resource.authHint ?? "-",
213
+ formatPricing(resource),
214
+ resource.source,
215
+ asYesNo(resource.verified),
216
+ resource.authHint === "siwx" ? "siwx" : "-"
217
+ ]);
218
+ }
219
+ function renderHeader(run, options, palette, withVerboseSuffix) {
220
+ const ok = run.result.resources.length > 0;
221
+ const status = ok ? palette.green("PASS") : palette.red("FAIL");
222
+ const warningSummary = summarizeWarnings(run.result.warnings);
223
+ const mode = options.compatMode;
224
+ return [
225
+ `${palette.bold("agentcash discovery audit")}${withVerboseSuffix ? ` ${palette.cyan("(verbose)")}` : ""}`,
226
+ `target: ${options.target}`,
227
+ `origin: ${run.result.origin}`,
228
+ `status: ${status}`,
229
+ `resources: ${run.result.resources.length}`,
230
+ `selected_stage: ${run.result.selectedStage ?? "none"}`,
231
+ `compat_mode: ${mode}`,
232
+ `upgrade_suggested: ${run.result.upgradeSuggested ? palette.yellow("yes") : "no"}`,
233
+ `duration_ms: ${run.durationMs}`,
234
+ `warnings: total=${warningSummary.total} error=${warningSummary.errors} warn=${warningSummary.warns} info=${warningSummary.infos}`,
235
+ warningSummary.codes.length > 0 ? `warning_codes: ${warningSummary.codes.join(", ")}` : "warning_codes: -",
236
+ options.probe ? `probe_candidates: ${run.probeCandidateCount}` : "probe_candidates: disabled"
237
+ ].join("\n");
238
+ }
239
+ function renderSummary(run, options) {
240
+ const palette = createPalette(options.color);
241
+ return renderHeader(run, options, palette, false);
242
+ }
243
+ function renderVerbose(run, options) {
244
+ const palette = createPalette(options.color);
245
+ const parts = [renderHeader(run, options, palette, true), ""];
246
+ parts.push(palette.bold("stage_matrix"));
247
+ parts.push(
248
+ createTable(
249
+ ["stage", "valid", "resources", "duration", "warning_codes", "selected"],
250
+ stageRows(run.result)
251
+ )
252
+ );
253
+ parts.push("");
254
+ parts.push(palette.bold("resource_matrix"));
255
+ if (run.result.resources.length === 0) {
256
+ parts.push(palette.dim("(no discovered resources)"));
257
+ } else {
258
+ parts.push(
259
+ createTable(
260
+ ["method", "path", "auth", "pricing", "source", "verified", "notes"],
261
+ resourceRows(run.result)
262
+ )
263
+ );
264
+ }
265
+ if (run.result.upgradeReasons.length > 0) {
266
+ parts.push("");
267
+ parts.push(palette.bold("upgrade_reasons"));
268
+ for (const reason of run.result.upgradeReasons) {
269
+ parts.push(`- ${reason}`);
270
+ }
271
+ }
272
+ return parts.join("\n");
273
+ }
274
+ function renderJson(run, options) {
275
+ return JSON.stringify(
276
+ {
277
+ ok: run.result.resources.length > 0,
278
+ target: options.target,
279
+ origin: run.result.origin,
280
+ compatMode: run.result.compatMode,
281
+ selectedStage: run.result.selectedStage ?? null,
282
+ resources: run.result.resources,
283
+ trace: run.result.trace,
284
+ warnings: run.result.warnings,
285
+ upgradeSuggested: run.result.upgradeSuggested,
286
+ upgradeReasons: run.result.upgradeReasons,
287
+ meta: {
288
+ durationMs: run.durationMs,
289
+ probeEnabled: options.probe,
290
+ probeCandidateCount: run.probeCandidateCount,
291
+ timeoutMs: options.timeoutMs
292
+ }
293
+ },
294
+ null,
295
+ 2
296
+ );
297
+ }
298
+
299
+ // src/core/constants.ts
300
+ var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
301
+ var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
302
+ var LLMS_TOKEN_WARNING_THRESHOLD = 2500;
303
+ var HTTP_METHODS = /* @__PURE__ */ new Set([
304
+ "GET",
305
+ "POST",
306
+ "PUT",
307
+ "DELETE",
308
+ "PATCH",
309
+ "HEAD",
310
+ "OPTIONS",
311
+ "TRACE"
312
+ ]);
313
+ var DEFAULT_PROBE_METHODS = ["GET", "POST"];
314
+ var DEFAULT_MISSING_METHOD = "POST";
315
+
316
+ // src/core/url.ts
317
+ function normalizeOrigin(target) {
318
+ const trimmed = target.trim();
319
+ const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
320
+ const url = new URL(withProtocol);
321
+ url.pathname = "";
322
+ url.search = "";
323
+ url.hash = "";
324
+ return url.toString().replace(/\/$/, "");
325
+ }
326
+ function normalizePath(pathname) {
327
+ const parsed = pathname.trim();
328
+ if (parsed.length === 0 || parsed === "/") return "/";
329
+ const pathOnly = parsed.split("?")[0]?.split("#")[0] ?? "/";
330
+ const prefixed = pathOnly.startsWith("/") ? pathOnly : `/${pathOnly}`;
331
+ const normalized = prefixed.replace(/\/+/g, "/");
332
+ return normalized !== "/" ? normalized.replace(/\/$/, "") : "/";
333
+ }
334
+ function toResourceKey(origin, method, path) {
335
+ return `${origin} ${method} ${normalizePath(path)}`;
336
+ }
337
+ function parseMethod(value) {
338
+ if (!value) return void 0;
339
+ const upper = value.toUpperCase();
340
+ return HTTP_METHODS.has(upper) ? upper : void 0;
341
+ }
342
+ function toAbsoluteUrl(origin, value) {
343
+ try {
344
+ if (/^https?:\/\//i.test(value)) return new URL(value);
345
+ return new URL(normalizePath(value), `${origin}/`);
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+
351
+ // src/core/warnings.ts
352
+ function warning(code, severity, message, options) {
353
+ return {
354
+ code,
355
+ severity,
356
+ message,
357
+ ...options?.hint ? { hint: options.hint } : {},
358
+ ...options?.stage ? { stage: options.stage } : {},
359
+ ...options?.resourceKey ? { resourceKey: options.resourceKey } : {}
360
+ };
361
+ }
362
+ function applyStrictEscalation(warnings, strict) {
363
+ if (!strict) return warnings;
364
+ return warnings.map((entry) => {
365
+ if (!STRICT_ESCALATION_CODES.includes(entry.code)) {
366
+ return entry;
367
+ }
368
+ if (entry.severity === "error") return entry;
369
+ return {
370
+ ...entry,
371
+ severity: "error",
372
+ message: `${entry.message} (strict mode escalated)`
373
+ };
374
+ });
375
+ }
376
+ function dedupeWarnings(warnings) {
377
+ const seen = /* @__PURE__ */ new Set();
378
+ const output = [];
379
+ for (const item of warnings) {
380
+ const key = `${item.code}|${item.severity}|${item.stage ?? ""}|${item.resourceKey ?? ""}|${item.message}`;
381
+ if (seen.has(key)) continue;
382
+ seen.add(key);
383
+ output.push(item);
384
+ }
385
+ return output;
386
+ }
387
+
388
+ // src/core/normalize.ts
389
+ function createResource(input, confidence, trustTier) {
390
+ const normalizedOrigin = normalizeOrigin(input.origin);
391
+ const normalizedPath = normalizePath(input.path);
392
+ return {
393
+ resourceKey: toResourceKey(normalizedOrigin, input.method, normalizedPath),
394
+ origin: normalizedOrigin,
395
+ method: input.method,
396
+ path: normalizedPath,
397
+ source: input.source,
398
+ verified: false,
399
+ ...input.protocolHints?.length ? { protocolHints: [...new Set(input.protocolHints)] } : {},
400
+ ...input.priceHint ? { priceHint: input.priceHint } : {},
401
+ ...input.pricing ? { pricing: input.pricing } : {},
402
+ ...input.authHint ? { authHint: input.authHint } : {},
403
+ ...input.summary ? { summary: input.summary } : {},
404
+ confidence,
405
+ trustTier,
406
+ ...input.links ? { links: input.links } : {}
407
+ };
408
+ }
409
+
410
+ // src/compat/legacy-x402scan/wellKnown.ts
411
+ function isRecord(value) {
412
+ return value !== null && typeof value === "object" && !Array.isArray(value);
413
+ }
414
+ function parseLegacyResourceEntry(entry) {
415
+ const trimmed = entry.trim();
416
+ if (!trimmed) return null;
417
+ const parts = trimmed.split(/\s+/);
418
+ if (parts.length >= 2) {
419
+ const maybeMethod = parseMethod(parts[0]);
420
+ if (maybeMethod) {
421
+ const target = parts.slice(1).join(" ");
422
+ return { method: maybeMethod, target };
423
+ }
424
+ }
425
+ if (trimmed.startsWith("/") || /^https?:\/\//i.test(trimmed)) {
426
+ return { target: trimmed };
427
+ }
428
+ return null;
429
+ }
430
+ function parseWellKnownPayload(payload, origin, sourceUrl) {
431
+ const warnings = [
432
+ warning(
433
+ "LEGACY_WELL_KNOWN_USED",
434
+ "warn",
435
+ "Using legacy /.well-known/x402 compatibility path. Migrate to OpenAPI-first.",
436
+ {
437
+ stage: "well-known/x402"
438
+ }
439
+ )
440
+ ];
441
+ if (!isRecord(payload)) {
442
+ warnings.push(
443
+ warning("PARSE_FAILED", "error", "Legacy well-known payload is not an object", {
444
+ stage: "well-known/x402"
445
+ })
446
+ );
447
+ return { resources: [], warnings, raw: payload };
448
+ }
449
+ const resourcesRaw = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
450
+ if (resourcesRaw.length === 0) {
451
+ warnings.push(
452
+ warning("STAGE_EMPTY", "warn", "Legacy well-known has no valid resources array", {
453
+ stage: "well-known/x402"
454
+ })
455
+ );
456
+ }
457
+ const instructions = typeof payload.instructions === "string" ? payload.instructions : void 0;
458
+ const ownershipProofs = Array.isArray(payload.ownershipProofs) ? payload.ownershipProofs.filter((entry) => typeof entry === "string") : [];
459
+ if (instructions) {
460
+ warnings.push(
461
+ warning(
462
+ "LEGACY_INSTRUCTIONS_USED",
463
+ "warn",
464
+ "Using /.well-known/x402.instructions as compatibility guidance fallback. Prefer llms.txt.",
465
+ {
466
+ stage: "well-known/x402",
467
+ hint: "Move guidance to llms.txt and reference via x-agentcash-guidance.llmsTxtUrl."
468
+ }
469
+ )
470
+ );
471
+ }
472
+ if (ownershipProofs.length > 0) {
473
+ warnings.push(
474
+ warning(
475
+ "LEGACY_OWNERSHIP_PROOFS_USED",
476
+ "warn",
477
+ "Using /.well-known/x402.ownershipProofs compatibility field. Prefer OpenAPI provenance extension.",
478
+ {
479
+ stage: "well-known/x402",
480
+ hint: "Move ownership proofs to x-agentcash-provenance.ownershipProofs in OpenAPI."
481
+ }
482
+ )
483
+ );
484
+ }
485
+ const resources = [];
486
+ for (const rawEntry of resourcesRaw) {
487
+ const parsed = parseLegacyResourceEntry(rawEntry);
488
+ if (!parsed) {
489
+ warnings.push(
490
+ warning("PARSE_FAILED", "warn", `Invalid legacy resource entry: ${rawEntry}`, {
491
+ stage: "well-known/x402"
492
+ })
493
+ );
494
+ continue;
495
+ }
496
+ const absolute = toAbsoluteUrl(origin, parsed.target);
497
+ if (!absolute) {
498
+ warnings.push(
499
+ warning("PARSE_FAILED", "warn", `Invalid legacy resource URL: ${rawEntry}`, {
500
+ stage: "well-known/x402"
501
+ })
502
+ );
503
+ continue;
504
+ }
505
+ const method = parsed.method ?? DEFAULT_MISSING_METHOD;
506
+ if (!parsed.method) {
507
+ warnings.push(
508
+ warning(
509
+ "LEGACY_MISSING_METHOD",
510
+ "warn",
511
+ `Legacy resource '${rawEntry}' missing method. Defaulting to ${DEFAULT_MISSING_METHOD}.`,
512
+ {
513
+ stage: "well-known/x402"
514
+ }
515
+ )
516
+ );
517
+ }
518
+ const path = normalizePath(absolute.pathname);
519
+ resources.push(
520
+ createResource(
521
+ {
522
+ origin: absolute.origin,
523
+ method,
524
+ path,
525
+ source: "well-known/x402",
526
+ summary: `${method} ${path}`,
527
+ authHint: "paid",
528
+ protocolHints: ["x402"],
529
+ links: {
530
+ wellKnownUrl: sourceUrl,
531
+ discoveryUrl: sourceUrl
532
+ }
533
+ },
534
+ 0.65,
535
+ ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
536
+ )
537
+ );
538
+ }
539
+ return {
540
+ resources,
541
+ warnings,
542
+ raw: payload
543
+ };
544
+ }
545
+ async function runWellKnownX402Stage(options) {
546
+ const stageUrl = options.url ?? `${options.origin}/.well-known/x402`;
547
+ try {
548
+ const response = await options.fetcher(stageUrl, {
549
+ method: "GET",
550
+ headers: { Accept: "application/json", ...options.headers },
551
+ signal: options.signal
552
+ });
553
+ if (!response.ok) {
554
+ if (response.status === 404) {
555
+ return {
556
+ stage: "well-known/x402",
557
+ valid: false,
558
+ resources: [],
559
+ warnings: [],
560
+ links: { wellKnownUrl: stageUrl }
561
+ };
562
+ }
563
+ return {
564
+ stage: "well-known/x402",
565
+ valid: false,
566
+ resources: [],
567
+ warnings: [
568
+ warning("FETCH_FAILED", "warn", `Legacy well-known fetch failed (${response.status})`, {
569
+ stage: "well-known/x402"
570
+ })
571
+ ],
572
+ links: { wellKnownUrl: stageUrl }
573
+ };
574
+ }
575
+ const payload = await response.json();
576
+ const parsed = parseWellKnownPayload(payload, options.origin, stageUrl);
577
+ return {
578
+ stage: "well-known/x402",
579
+ valid: parsed.resources.length > 0,
580
+ resources: parsed.resources,
581
+ warnings: parsed.warnings,
582
+ links: { wellKnownUrl: stageUrl },
583
+ ...options.includeRaw ? { raw: parsed.raw } : {}
584
+ };
585
+ } catch (error) {
586
+ return {
587
+ stage: "well-known/x402",
588
+ valid: false,
589
+ resources: [],
590
+ warnings: [
591
+ warning(
592
+ "FETCH_FAILED",
593
+ "warn",
594
+ `Legacy well-known fetch exception: ${error instanceof Error ? error.message : String(error)}`,
595
+ {
596
+ stage: "well-known/x402"
597
+ }
598
+ )
599
+ ],
600
+ links: { wellKnownUrl: stageUrl }
601
+ };
602
+ }
603
+ }
604
+
605
+ // src/compat/legacy-x402scan/dns.ts
606
+ function parseDnsRecord(record) {
607
+ const trimmed = record.trim();
608
+ if (!trimmed) return null;
609
+ if (/^https?:\/\//i.test(trimmed)) {
610
+ return { url: trimmed, legacyPlainUrl: true };
611
+ }
612
+ const parts = trimmed.split(";").map((entry) => entry.trim()).filter(Boolean);
613
+ const keyValues = /* @__PURE__ */ new Map();
614
+ for (const part of parts) {
615
+ const separator = part.indexOf("=");
616
+ if (separator <= 0) continue;
617
+ const key = part.slice(0, separator).trim().toLowerCase();
618
+ const value = part.slice(separator + 1).trim();
619
+ if (key && value) keyValues.set(key, value);
620
+ }
621
+ if (keyValues.get("v") !== "x4021") return null;
622
+ const url = keyValues.get("url");
623
+ if (!url || !/^https?:\/\//i.test(url)) return null;
624
+ return { url, legacyPlainUrl: false };
625
+ }
626
+ async function runDnsStage(options) {
627
+ if (!options.txtResolver) {
628
+ return {
629
+ stage: "dns/_x402",
630
+ valid: false,
631
+ resources: [],
632
+ warnings: []
633
+ };
634
+ }
635
+ const origin = normalizeOrigin(options.origin);
636
+ const hostname = new URL(origin).hostname;
637
+ const fqdn = `_x402.${hostname}`;
638
+ let records;
639
+ try {
640
+ records = await options.txtResolver(fqdn);
641
+ } catch (error) {
642
+ return {
643
+ stage: "dns/_x402",
644
+ valid: false,
645
+ resources: [],
646
+ warnings: [
647
+ warning(
648
+ "FETCH_FAILED",
649
+ "warn",
650
+ `DNS TXT lookup failed for ${fqdn}: ${error instanceof Error ? error.message : String(error)}`,
651
+ { stage: "dns/_x402" }
652
+ )
653
+ ]
654
+ };
655
+ }
656
+ if (records.length === 0) {
657
+ return {
658
+ stage: "dns/_x402",
659
+ valid: false,
660
+ resources: [],
661
+ warnings: []
662
+ };
663
+ }
664
+ const warnings = [
665
+ warning(
666
+ "LEGACY_DNS_USED",
667
+ "warn",
668
+ "Using DNS _x402 compatibility path. Migrate to OpenAPI-first discovery.",
669
+ {
670
+ stage: "dns/_x402"
671
+ }
672
+ )
673
+ ];
674
+ const urls = [];
675
+ for (const record of records) {
676
+ const parsed = parseDnsRecord(record);
677
+ if (!parsed) {
678
+ warnings.push(
679
+ warning("PARSE_FAILED", "warn", `Invalid DNS _x402 TXT record: ${record}`, {
680
+ stage: "dns/_x402"
681
+ })
682
+ );
683
+ continue;
684
+ }
685
+ urls.push(parsed.url);
686
+ if (parsed.legacyPlainUrl) {
687
+ warnings.push(
688
+ warning("LEGACY_DNS_PLAIN_URL", "warn", `Legacy plain URL TXT format used: ${record}`, {
689
+ stage: "dns/_x402",
690
+ hint: "Use v=x4021;url=<https-url> format."
691
+ })
692
+ );
693
+ }
694
+ }
695
+ if (urls.length === 0) {
696
+ return {
697
+ stage: "dns/_x402",
698
+ valid: false,
699
+ resources: [],
700
+ warnings,
701
+ ...options.includeRaw ? { raw: { dnsRecords: records } } : {}
702
+ };
703
+ }
704
+ const mergedWarnings = [...warnings];
705
+ const mergedResources = [];
706
+ const rawDocuments = [];
707
+ for (const url of urls) {
708
+ const stageResult = await runWellKnownX402Stage({
709
+ origin,
710
+ url,
711
+ fetcher: options.fetcher,
712
+ headers: options.headers,
713
+ signal: options.signal,
714
+ includeRaw: options.includeRaw
715
+ });
716
+ mergedResources.push(...stageResult.resources);
717
+ mergedWarnings.push(...stageResult.warnings);
718
+ if (options.includeRaw && stageResult.raw !== void 0) {
719
+ rawDocuments.push({ url, document: stageResult.raw });
720
+ }
721
+ }
722
+ const deduped = /* @__PURE__ */ new Map();
723
+ for (const resource of mergedResources) {
724
+ if (!deduped.has(resource.resourceKey)) {
725
+ deduped.set(resource.resourceKey, {
726
+ ...resource,
727
+ source: "dns/_x402",
728
+ links: {
729
+ ...resource.links,
730
+ discoveryUrl: resource.links?.discoveryUrl
731
+ }
732
+ });
733
+ }
734
+ }
735
+ return {
736
+ stage: "dns/_x402",
737
+ valid: deduped.size > 0,
738
+ resources: [...deduped.values()],
739
+ warnings: mergedWarnings,
740
+ ...options.includeRaw ? { raw: { dnsRecords: records, documents: rawDocuments } } : {}
741
+ };
742
+ }
743
+
744
+ // src/compat/interop-mpp/wellKnownMpp.ts
745
+ function isRecord2(value) {
746
+ return value !== null && typeof value === "object" && !Array.isArray(value);
747
+ }
748
+ async function runInteropMppStage(options) {
749
+ const url = `${options.origin}${WELL_KNOWN_MPP_PATH}`;
750
+ try {
751
+ const response = await options.fetcher(url, {
752
+ method: "GET",
753
+ headers: { Accept: "application/json", ...options.headers },
754
+ signal: options.signal
755
+ });
756
+ if (!response.ok) {
757
+ return {
758
+ stage: "interop/mpp",
759
+ valid: false,
760
+ resources: [],
761
+ warnings: []
762
+ };
763
+ }
764
+ const payload = await response.json();
765
+ if (!isRecord2(payload)) {
766
+ return {
767
+ stage: "interop/mpp",
768
+ valid: false,
769
+ resources: [],
770
+ warnings: [
771
+ warning("PARSE_FAILED", "warn", "Interop /.well-known/mpp payload is not an object", {
772
+ stage: "interop/mpp"
773
+ })
774
+ ],
775
+ ...options.includeRaw ? { raw: payload } : {}
776
+ };
777
+ }
778
+ const warnings = [
779
+ warning(
780
+ "INTEROP_MPP_USED",
781
+ "info",
782
+ "Using /.well-known/mpp interop adapter. This is additive and non-canonical.",
783
+ {
784
+ stage: "interop/mpp",
785
+ hint: "Use for registry indexing only, not canonical runtime routing."
786
+ }
787
+ )
788
+ ];
789
+ const resources = [];
790
+ const fromStringResources = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
791
+ for (const entry of fromStringResources) {
792
+ const parts = entry.trim().split(/\s+/);
793
+ let method = parseMethod(parts[0]);
794
+ let path = parts.join(" ");
795
+ if (method) {
796
+ path = parts.slice(1).join(" ");
797
+ } else {
798
+ method = "POST";
799
+ }
800
+ const normalizedPath = normalizePath(path);
801
+ resources.push(
802
+ createResource(
803
+ {
804
+ origin: options.origin,
805
+ method,
806
+ path: normalizedPath,
807
+ source: "interop/mpp",
808
+ summary: `${method} ${normalizedPath}`,
809
+ authHint: "paid",
810
+ protocolHints: ["mpp"],
811
+ links: { discoveryUrl: url }
812
+ },
813
+ 0.45,
814
+ "unverified"
815
+ )
816
+ );
817
+ }
818
+ const fromServiceObjects = Array.isArray(payload.services) ? payload.services.filter((entry) => isRecord2(entry)) : [];
819
+ for (const service of fromServiceObjects) {
820
+ const path = typeof service.path === "string" ? service.path : void 0;
821
+ const method = parseMethod(typeof service.method === "string" ? service.method : void 0) ?? "POST";
822
+ if (!path) continue;
823
+ const normalizedPath = normalizePath(path);
824
+ resources.push(
825
+ createResource(
826
+ {
827
+ origin: options.origin,
828
+ method,
829
+ path: normalizedPath,
830
+ source: "interop/mpp",
831
+ summary: typeof service.summary === "string" ? service.summary : `${method} ${normalizedPath}`,
832
+ authHint: "paid",
833
+ protocolHints: ["mpp"],
834
+ links: { discoveryUrl: url }
835
+ },
836
+ 0.45,
837
+ "unverified"
838
+ )
839
+ );
840
+ }
841
+ const deduped = /* @__PURE__ */ new Map();
842
+ for (const resource of resources) {
843
+ if (!deduped.has(resource.resourceKey)) deduped.set(resource.resourceKey, resource);
844
+ }
845
+ return {
846
+ stage: "interop/mpp",
847
+ valid: deduped.size > 0,
848
+ resources: [...deduped.values()],
849
+ warnings,
850
+ ...options.includeRaw ? { raw: payload } : {}
851
+ };
852
+ } catch (error) {
853
+ return {
854
+ stage: "interop/mpp",
855
+ valid: false,
856
+ resources: [],
857
+ warnings: [
858
+ warning(
859
+ "FETCH_FAILED",
860
+ "warn",
861
+ `Interop /.well-known/mpp fetch failed: ${error instanceof Error ? error.message : String(error)}`,
862
+ {
863
+ stage: "interop/mpp"
864
+ }
865
+ )
866
+ ]
867
+ };
868
+ }
869
+ }
870
+
871
+ // src/core/token.ts
872
+ function estimateTokenCount(text) {
873
+ return Math.ceil(text.length / 4);
874
+ }
875
+
876
+ // src/core/openapi.ts
877
+ function isRecord3(value) {
878
+ return value !== null && typeof value === "object" && !Array.isArray(value);
879
+ }
880
+ function asString(value) {
881
+ return typeof value === "string" && value.length > 0 ? value : void 0;
882
+ }
883
+ function parsePriceValue(value) {
884
+ if (typeof value === "string" && value.length > 0) return value;
885
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
886
+ return void 0;
887
+ }
888
+ function require402Response(operation) {
889
+ const responses = operation.responses;
890
+ if (!isRecord3(responses)) return false;
891
+ return Boolean(responses["402"]);
892
+ }
893
+ function parseProtocols(paymentInfo) {
894
+ const protocols = paymentInfo.protocols;
895
+ if (!Array.isArray(protocols)) return [];
896
+ return protocols.filter(
897
+ (entry) => typeof entry === "string" && entry.length > 0
898
+ );
899
+ }
900
+ function parseAuthMode(operation) {
901
+ const auth = operation["x-agentcash-auth"];
902
+ if (!isRecord3(auth)) return void 0;
903
+ const mode = auth.mode;
904
+ if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
905
+ return mode;
906
+ }
907
+ return void 0;
908
+ }
909
+ async function runOpenApiStage(options) {
910
+ const warnings = [];
911
+ const resources = [];
912
+ let fetchedUrl;
913
+ let document;
914
+ for (const path of OPENAPI_PATH_CANDIDATES) {
915
+ const url = `${options.origin}${path}`;
916
+ try {
917
+ const response = await options.fetcher(url, {
918
+ method: "GET",
919
+ headers: { Accept: "application/json", ...options.headers },
920
+ signal: options.signal
921
+ });
922
+ if (!response.ok) {
923
+ if (response.status !== 404) {
924
+ warnings.push(
925
+ warning("FETCH_FAILED", "warn", `OpenAPI fetch failed (${response.status}) at ${url}`, {
926
+ stage: "openapi"
927
+ })
928
+ );
929
+ }
930
+ continue;
931
+ }
932
+ const payload = await response.json();
933
+ if (!isRecord3(payload)) {
934
+ warnings.push(
935
+ warning("PARSE_FAILED", "error", `OpenAPI payload at ${url} is not a JSON object`, {
936
+ stage: "openapi"
937
+ })
938
+ );
939
+ continue;
940
+ }
941
+ document = payload;
942
+ fetchedUrl = url;
943
+ break;
944
+ } catch (error) {
945
+ warnings.push(
946
+ warning(
947
+ "FETCH_FAILED",
948
+ "warn",
949
+ `OpenAPI fetch exception at ${url}: ${error instanceof Error ? error.message : String(error)}`,
950
+ { stage: "openapi" }
951
+ )
952
+ );
953
+ }
954
+ }
955
+ if (!document || !fetchedUrl) {
956
+ return {
957
+ stage: "openapi",
958
+ valid: false,
959
+ resources: [],
960
+ warnings
961
+ };
962
+ }
963
+ const hasTopLevel = typeof document.openapi === "string" && isRecord3(document.info) && typeof document.info.title === "string" && typeof document.info.version === "string" && isRecord3(document.paths);
964
+ if (!hasTopLevel) {
965
+ warnings.push(
966
+ warning("OPENAPI_TOP_LEVEL_INVALID", "error", "OpenAPI required fields are missing", {
967
+ stage: "openapi"
968
+ })
969
+ );
970
+ return {
971
+ stage: "openapi",
972
+ valid: false,
973
+ resources: [],
974
+ warnings,
975
+ links: { openapiUrl: fetchedUrl },
976
+ ...options.includeRaw ? { raw: document } : {}
977
+ };
978
+ }
979
+ const paths = document.paths;
980
+ const provenance = isRecord3(document["x-agentcash-provenance"]) ? document["x-agentcash-provenance"] : void 0;
981
+ const ownershipProofs = Array.isArray(provenance?.ownershipProofs) ? provenance.ownershipProofs.filter((entry) => typeof entry === "string") : [];
982
+ const guidance = isRecord3(document["x-agentcash-guidance"]) ? document["x-agentcash-guidance"] : void 0;
983
+ const llmsTxtUrl = asString(guidance?.llmsTxtUrl);
984
+ if (ownershipProofs.length > 0) {
985
+ warnings.push(
986
+ warning("OPENAPI_OWNERSHIP_PROOFS_PRESENT", "info", "OpenAPI ownership proofs detected", {
987
+ stage: "openapi"
988
+ })
989
+ );
990
+ }
991
+ for (const [rawPath, rawPathItem] of Object.entries(paths)) {
992
+ if (!isRecord3(rawPathItem)) {
993
+ warnings.push(
994
+ warning("OPENAPI_OPERATION_INVALID", "warn", `Path item ${rawPath} is not an object`, {
995
+ stage: "openapi"
996
+ })
997
+ );
998
+ continue;
999
+ }
1000
+ for (const [rawMethod, rawOperation] of Object.entries(rawPathItem)) {
1001
+ const method = parseMethod(rawMethod);
1002
+ if (!method) continue;
1003
+ if (!isRecord3(rawOperation)) {
1004
+ warnings.push(
1005
+ warning(
1006
+ "OPENAPI_OPERATION_INVALID",
1007
+ "warn",
1008
+ `${rawMethod.toUpperCase()} ${rawPath} is not an object`,
1009
+ {
1010
+ stage: "openapi"
1011
+ }
1012
+ )
1013
+ );
1014
+ continue;
1015
+ }
1016
+ const operation = rawOperation;
1017
+ const summary = asString(operation.summary) ?? asString(operation.description);
1018
+ if (!summary) {
1019
+ warnings.push(
1020
+ warning(
1021
+ "OPENAPI_SUMMARY_MISSING",
1022
+ "warn",
1023
+ `${method} ${rawPath} missing summary/description, using fallback summary`,
1024
+ { stage: "openapi" }
1025
+ )
1026
+ );
1027
+ }
1028
+ const authMode = parseAuthMode(operation);
1029
+ if (!authMode) {
1030
+ warnings.push(
1031
+ warning(
1032
+ "OPENAPI_AUTH_MODE_MISSING",
1033
+ "error",
1034
+ `${method} ${rawPath} missing x-agentcash-auth.mode`,
1035
+ {
1036
+ stage: "openapi"
1037
+ }
1038
+ )
1039
+ );
1040
+ continue;
1041
+ }
1042
+ if ((authMode === "paid" || authMode === "siwx") && !require402Response(operation)) {
1043
+ warnings.push(
1044
+ warning(
1045
+ "OPENAPI_402_MISSING",
1046
+ "error",
1047
+ `${method} ${rawPath} requires 402 response for authMode=${authMode}`,
1048
+ { stage: "openapi" }
1049
+ )
1050
+ );
1051
+ continue;
1052
+ }
1053
+ const paymentInfo = isRecord3(operation["x-payment-info"]) ? operation["x-payment-info"] : void 0;
1054
+ const protocols = paymentInfo ? parseProtocols(paymentInfo) : [];
1055
+ if (authMode === "paid" && protocols.length === 0) {
1056
+ warnings.push(
1057
+ warning(
1058
+ "OPENAPI_PAID_PROTOCOLS_MISSING",
1059
+ "error",
1060
+ `${method} ${rawPath} must define x-payment-info.protocols when authMode=paid`,
1061
+ { stage: "openapi" }
1062
+ )
1063
+ );
1064
+ continue;
1065
+ }
1066
+ let pricing;
1067
+ let priceHint;
1068
+ if (paymentInfo && authMode === "paid") {
1069
+ const pricingModeRaw = asString(paymentInfo.pricingMode);
1070
+ const price = parsePriceValue(paymentInfo.price);
1071
+ const minPrice = parsePriceValue(paymentInfo.minPrice);
1072
+ const maxPrice = parsePriceValue(paymentInfo.maxPrice);
1073
+ const inferredPricingMode = pricingModeRaw ?? (price ? "fixed" : minPrice && maxPrice ? "range" : "quote");
1074
+ if (inferredPricingMode !== "fixed" && inferredPricingMode !== "range" && inferredPricingMode !== "quote") {
1075
+ warnings.push(
1076
+ warning(
1077
+ "OPENAPI_PRICING_INVALID",
1078
+ "error",
1079
+ `${method} ${rawPath} has invalid pricingMode`,
1080
+ {
1081
+ stage: "openapi"
1082
+ }
1083
+ )
1084
+ );
1085
+ continue;
1086
+ }
1087
+ if (inferredPricingMode === "fixed" && !price) {
1088
+ warnings.push(
1089
+ warning(
1090
+ "OPENAPI_PRICING_INVALID",
1091
+ "error",
1092
+ `${method} ${rawPath} fixed pricing requires price`,
1093
+ {
1094
+ stage: "openapi"
1095
+ }
1096
+ )
1097
+ );
1098
+ continue;
1099
+ }
1100
+ if (inferredPricingMode === "range") {
1101
+ if (!minPrice || !maxPrice) {
1102
+ warnings.push(
1103
+ warning(
1104
+ "OPENAPI_PRICING_INVALID",
1105
+ "error",
1106
+ `${method} ${rawPath} range pricing requires minPrice and maxPrice`,
1107
+ { stage: "openapi" }
1108
+ )
1109
+ );
1110
+ continue;
1111
+ }
1112
+ const min = Number(minPrice);
1113
+ const max = Number(maxPrice);
1114
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min > max) {
1115
+ warnings.push(
1116
+ warning(
1117
+ "OPENAPI_PRICING_INVALID",
1118
+ "error",
1119
+ `${method} ${rawPath} range pricing requires numeric minPrice <= maxPrice`,
1120
+ { stage: "openapi" }
1121
+ )
1122
+ );
1123
+ continue;
1124
+ }
1125
+ }
1126
+ pricing = {
1127
+ pricingMode: inferredPricingMode,
1128
+ ...price ? { price } : {},
1129
+ ...minPrice ? { minPrice } : {},
1130
+ ...maxPrice ? { maxPrice } : {}
1131
+ };
1132
+ priceHint = inferredPricingMode === "fixed" ? price : inferredPricingMode === "range" ? `${minPrice}-${maxPrice}` : maxPrice;
1133
+ }
1134
+ const resource = createResource(
1135
+ {
1136
+ origin: options.origin,
1137
+ method,
1138
+ path: normalizePath(rawPath),
1139
+ source: "openapi",
1140
+ summary: summary ?? `${method} ${normalizePath(rawPath)}`,
1141
+ authHint: authMode,
1142
+ protocolHints: protocols,
1143
+ ...priceHint ? { priceHint } : {},
1144
+ ...pricing ? { pricing } : {},
1145
+ links: {
1146
+ openapiUrl: fetchedUrl,
1147
+ ...llmsTxtUrl ? { llmsTxtUrl } : {}
1148
+ }
1149
+ },
1150
+ 0.95,
1151
+ ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
1152
+ );
1153
+ resources.push(resource);
1154
+ }
1155
+ }
1156
+ if (llmsTxtUrl) {
1157
+ try {
1158
+ const llmsResponse = await options.fetcher(llmsTxtUrl, {
1159
+ method: "GET",
1160
+ headers: { Accept: "text/plain", ...options.headers },
1161
+ signal: options.signal
1162
+ });
1163
+ if (llmsResponse.ok) {
1164
+ const llmsText = await llmsResponse.text();
1165
+ const tokenCount = estimateTokenCount(llmsText);
1166
+ if (tokenCount > LLMS_TOKEN_WARNING_THRESHOLD) {
1167
+ warnings.push(
1168
+ warning(
1169
+ "LLMSTXT_TOO_LARGE",
1170
+ "warn",
1171
+ `llms.txt estimated ${tokenCount} tokens (threshold ${LLMS_TOKEN_WARNING_THRESHOLD})`,
1172
+ {
1173
+ stage: "openapi",
1174
+ hint: "Keep llms.txt concise and domain-level only."
1175
+ }
1176
+ )
1177
+ );
1178
+ }
1179
+ if (options.includeRaw) {
1180
+ return {
1181
+ stage: "openapi",
1182
+ valid: resources.length > 0,
1183
+ resources,
1184
+ warnings,
1185
+ links: { openapiUrl: fetchedUrl, llmsTxtUrl },
1186
+ raw: {
1187
+ openapi: document,
1188
+ llmsTxt: llmsText
1189
+ }
1190
+ };
1191
+ }
1192
+ } else {
1193
+ warnings.push(
1194
+ warning(
1195
+ "LLMSTXT_FETCH_FAILED",
1196
+ "warn",
1197
+ `llms.txt fetch failed (${llmsResponse.status})`,
1198
+ {
1199
+ stage: "openapi"
1200
+ }
1201
+ )
1202
+ );
1203
+ }
1204
+ } catch (error) {
1205
+ warnings.push(
1206
+ warning(
1207
+ "LLMSTXT_FETCH_FAILED",
1208
+ "warn",
1209
+ `llms.txt fetch failed: ${error instanceof Error ? error.message : String(error)}`,
1210
+ { stage: "openapi" }
1211
+ )
1212
+ );
1213
+ }
1214
+ }
1215
+ return {
1216
+ stage: "openapi",
1217
+ valid: resources.length > 0,
1218
+ resources,
1219
+ warnings,
1220
+ links: {
1221
+ openapiUrl: fetchedUrl,
1222
+ ...llmsTxtUrl ? { llmsTxtUrl } : {}
1223
+ },
1224
+ ...options.includeRaw ? { raw: { openapi: document } } : {}
1225
+ };
1226
+ }
1227
+
1228
+ // src/core/probe.ts
1229
+ function detectAuthHintFrom402Payload(payload) {
1230
+ if (payload && typeof payload === "object") {
1231
+ const asRecord = payload;
1232
+ const extensions = asRecord.extensions;
1233
+ if (extensions && typeof extensions === "object") {
1234
+ const extRecord = extensions;
1235
+ if (extRecord["sign-in-with-x"]) return "siwx";
1236
+ }
1237
+ }
1238
+ return "paid";
1239
+ }
1240
+ function detectProtocols(response) {
1241
+ const protocols = /* @__PURE__ */ new Set();
1242
+ const directHeader = response.headers.get("x-payment-protocol");
1243
+ if (directHeader) {
1244
+ for (const part of directHeader.split(",")) {
1245
+ const protocol = part.trim().toLowerCase();
1246
+ if (protocol) protocols.add(protocol);
1247
+ }
1248
+ }
1249
+ const authHeader = response.headers.get("www-authenticate")?.toLowerCase() ?? "";
1250
+ if (authHeader.includes("x402")) protocols.add("x402");
1251
+ if (authHeader.includes("mpp")) protocols.add("mpp");
1252
+ return [...protocols];
1253
+ }
1254
+ async function runProbeStage(options) {
1255
+ const candidates = options.probeCandidates ?? [];
1256
+ if (candidates.length === 0) {
1257
+ return {
1258
+ stage: "probe",
1259
+ valid: false,
1260
+ resources: [],
1261
+ warnings: [
1262
+ warning(
1263
+ "PROBE_STAGE_SKIPPED",
1264
+ "info",
1265
+ "Probe stage skipped because no probe candidates were provided.",
1266
+ { stage: "probe" }
1267
+ )
1268
+ ]
1269
+ };
1270
+ }
1271
+ const resources = [];
1272
+ const warnings = [];
1273
+ for (const candidate of candidates) {
1274
+ const path = normalizePath(candidate.path);
1275
+ const methods = candidate.methods?.length ? candidate.methods : DEFAULT_PROBE_METHODS;
1276
+ for (const method of methods) {
1277
+ const url = `${options.origin}${path}`;
1278
+ try {
1279
+ const response = await options.fetcher(url, {
1280
+ method,
1281
+ headers: {
1282
+ Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
1283
+ ...options.headers
1284
+ },
1285
+ signal: options.signal
1286
+ });
1287
+ if (response.status !== 402 && (response.status < 200 || response.status >= 300)) {
1288
+ continue;
1289
+ }
1290
+ let authHint = response.status === 402 ? "paid" : "unprotected";
1291
+ if (response.status === 402) {
1292
+ try {
1293
+ const payload = await response.clone().json();
1294
+ authHint = detectAuthHintFrom402Payload(payload);
1295
+ } catch {
1296
+ }
1297
+ }
1298
+ const protocolHints = detectProtocols(response);
1299
+ resources.push(
1300
+ createResource(
1301
+ {
1302
+ origin: options.origin,
1303
+ method,
1304
+ path,
1305
+ source: "probe",
1306
+ summary: `${method} ${path}`,
1307
+ authHint,
1308
+ ...protocolHints.length ? { protocolHints } : {}
1309
+ },
1310
+ 0.6,
1311
+ "runtime_verified"
1312
+ )
1313
+ );
1314
+ } catch (error) {
1315
+ warnings.push(
1316
+ warning(
1317
+ "FETCH_FAILED",
1318
+ "info",
1319
+ `Probe ${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`,
1320
+ { stage: "probe" }
1321
+ )
1322
+ );
1323
+ }
1324
+ }
1325
+ }
1326
+ return {
1327
+ stage: "probe",
1328
+ valid: resources.length > 0,
1329
+ resources,
1330
+ warnings
1331
+ };
1332
+ }
1333
+
1334
+ // src/core/upgrade.ts
1335
+ var UPGRADE_WARNING_CODES = [
1336
+ "LEGACY_WELL_KNOWN_USED",
1337
+ "LEGACY_DNS_USED",
1338
+ "LEGACY_DNS_PLAIN_URL",
1339
+ "LEGACY_INSTRUCTIONS_USED",
1340
+ "LEGACY_OWNERSHIP_PROOFS_USED",
1341
+ "OPENAPI_AUTH_MODE_MISSING",
1342
+ "OPENAPI_TOP_LEVEL_INVALID"
1343
+ ];
1344
+ var UPGRADE_WARNING_CODE_SET = new Set(UPGRADE_WARNING_CODES);
1345
+ function computeUpgradeSignal(warnings) {
1346
+ const reasons = warnings.map((entry) => entry.code).filter((code, index, list) => list.indexOf(code) === index).filter((code) => UPGRADE_WARNING_CODE_SET.has(code));
1347
+ return {
1348
+ upgradeSuggested: reasons.length > 0,
1349
+ upgradeReasons: reasons
1350
+ };
1351
+ }
1352
+
1353
+ // src/core/discovery.ts
1354
+ function mergeResources(target, incoming, resourceWarnings) {
1355
+ const warnings = [];
1356
+ for (const resource of incoming) {
1357
+ const existing = target.get(resource.resourceKey);
1358
+ if (!existing) {
1359
+ target.set(resource.resourceKey, resource);
1360
+ continue;
1361
+ }
1362
+ const conflict = existing.authHint !== resource.authHint || existing.priceHint !== resource.priceHint || JSON.stringify(existing.protocolHints ?? []) !== JSON.stringify(resource.protocolHints ?? []);
1363
+ if (conflict) {
1364
+ const conflictWarning = warning(
1365
+ "CROSS_SOURCE_CONFLICT",
1366
+ "warn",
1367
+ `Resource conflict for ${resource.resourceKey}; keeping higher-precedence source ${existing.source} over ${resource.source}.`,
1368
+ {
1369
+ resourceKey: resource.resourceKey
1370
+ }
1371
+ );
1372
+ warnings.push(conflictWarning);
1373
+ resourceWarnings[resource.resourceKey] = [
1374
+ ...resourceWarnings[resource.resourceKey] ?? [],
1375
+ conflictWarning
1376
+ ];
1377
+ continue;
1378
+ }
1379
+ target.set(resource.resourceKey, {
1380
+ ...existing,
1381
+ summary: existing.summary ?? resource.summary,
1382
+ links: { ...resource.links, ...existing.links },
1383
+ confidence: Math.max(existing.confidence ?? 0, resource.confidence ?? 0)
1384
+ });
1385
+ }
1386
+ return warnings;
1387
+ }
1388
+ function toTrace(stageResult, startedAt) {
1389
+ return {
1390
+ stage: stageResult.stage,
1391
+ attempted: true,
1392
+ valid: stageResult.valid,
1393
+ resourceCount: stageResult.resources.length,
1394
+ durationMs: Date.now() - startedAt,
1395
+ warnings: stageResult.warnings,
1396
+ ...stageResult.links ? { links: stageResult.links } : {}
1397
+ };
1398
+ }
1399
+ async function runOverrideStage(options) {
1400
+ const warnings = [];
1401
+ for (const overrideUrl of options.overrideUrls) {
1402
+ const normalized = overrideUrl.trim();
1403
+ if (!normalized) continue;
1404
+ try {
1405
+ const response = await options.fetcher(normalized, {
1406
+ method: "GET",
1407
+ headers: { Accept: "application/json, text/plain;q=0.9", ...options.headers },
1408
+ signal: options.signal
1409
+ });
1410
+ if (!response.ok) {
1411
+ warnings.push(
1412
+ warning(
1413
+ "FETCH_FAILED",
1414
+ "warn",
1415
+ `Override fetch failed (${response.status}) at ${normalized}`,
1416
+ {
1417
+ stage: "override"
1418
+ }
1419
+ )
1420
+ );
1421
+ continue;
1422
+ }
1423
+ const bodyText = await response.text();
1424
+ let parsedJson;
1425
+ try {
1426
+ parsedJson = JSON.parse(bodyText);
1427
+ } catch {
1428
+ parsedJson = void 0;
1429
+ }
1430
+ if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson) && "openapi" in parsedJson && "paths" in parsedJson) {
1431
+ const openapiResult = await runOpenApiStage({
1432
+ origin: options.origin,
1433
+ fetcher: async (input, init) => {
1434
+ const inputString = String(input);
1435
+ if (inputString === `${options.origin}/openapi.json` || inputString === `${options.origin}/.well-known/openapi.json`) {
1436
+ return new Response(bodyText, {
1437
+ status: 200,
1438
+ headers: { "content-type": "application/json" }
1439
+ });
1440
+ }
1441
+ return options.fetcher(input, init);
1442
+ },
1443
+ headers: options.headers,
1444
+ signal: options.signal,
1445
+ includeRaw: options.includeRaw
1446
+ });
1447
+ return {
1448
+ ...openapiResult,
1449
+ stage: "override",
1450
+ warnings: [
1451
+ warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1452
+ stage: "override"
1453
+ }),
1454
+ ...openapiResult.warnings
1455
+ ]
1456
+ };
1457
+ }
1458
+ if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson)) {
1459
+ const parsedWellKnown = parseWellKnownPayload(parsedJson, options.origin, normalized);
1460
+ if (parsedWellKnown.resources.length > 0) {
1461
+ return {
1462
+ stage: "override",
1463
+ valid: true,
1464
+ resources: parsedWellKnown.resources,
1465
+ warnings: [
1466
+ warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1467
+ stage: "override"
1468
+ }),
1469
+ ...parsedWellKnown.warnings
1470
+ ],
1471
+ links: { discoveryUrl: normalized },
1472
+ ...options.includeRaw ? { raw: parsedWellKnown.raw } : {}
1473
+ };
1474
+ }
1475
+ }
1476
+ } catch (error) {
1477
+ warnings.push(
1478
+ warning(
1479
+ "FETCH_FAILED",
1480
+ "warn",
1481
+ `Override fetch exception at ${normalized}: ${error instanceof Error ? error.message : String(error)}`,
1482
+ { stage: "override" }
1483
+ )
1484
+ );
1485
+ continue;
1486
+ }
1487
+ const fallbackWellKnownResult = await runWellKnownX402Stage({
1488
+ origin: options.origin,
1489
+ url: normalized,
1490
+ fetcher: options.fetcher,
1491
+ headers: options.headers,
1492
+ signal: options.signal,
1493
+ includeRaw: options.includeRaw
1494
+ });
1495
+ if (fallbackWellKnownResult.valid) {
1496
+ return {
1497
+ ...fallbackWellKnownResult,
1498
+ stage: "override",
1499
+ warnings: [
1500
+ warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1501
+ stage: "override"
1502
+ }),
1503
+ ...fallbackWellKnownResult.warnings
1504
+ ]
1505
+ };
1506
+ }
1507
+ warnings.push(...fallbackWellKnownResult.warnings);
1508
+ }
1509
+ return {
1510
+ stage: "override",
1511
+ valid: false,
1512
+ resources: [],
1513
+ warnings
1514
+ };
1515
+ }
1516
+ async function runDiscovery({
1517
+ detailed,
1518
+ options
1519
+ }) {
1520
+ const fetcher = options.fetcher ?? fetch;
1521
+ const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
1522
+ const strictCompat = compatMode === "strict";
1523
+ const rawView = detailed ? options.rawView ?? "none" : "none";
1524
+ const includeRaw = rawView === "full";
1525
+ const includeInteropMpp = detailed && Boolean(options.includeInteropMpp);
1526
+ const origin = normalizeOrigin(options.target);
1527
+ const warnings = [];
1528
+ const trace = [];
1529
+ const resourceWarnings = {};
1530
+ const rawSources = {};
1531
+ const merged = /* @__PURE__ */ new Map();
1532
+ if (compatMode !== "off") {
1533
+ warnings.push(
1534
+ warning(
1535
+ "COMPAT_MODE_ENABLED",
1536
+ compatMode === "strict" ? "warn" : "info",
1537
+ `Compatibility mode is '${compatMode}'. Legacy adapters are active.`
1538
+ )
1539
+ );
1540
+ }
1541
+ const stages = [];
1542
+ if (options.overrideUrls && options.overrideUrls.length > 0) {
1543
+ stages.push(
1544
+ () => runOverrideStage({
1545
+ origin,
1546
+ overrideUrls: options.overrideUrls ?? [],
1547
+ fetcher,
1548
+ headers: options.headers,
1549
+ signal: options.signal,
1550
+ includeRaw
1551
+ })
1552
+ );
1553
+ }
1554
+ stages.push(
1555
+ () => runOpenApiStage({
1556
+ origin,
1557
+ fetcher,
1558
+ headers: options.headers,
1559
+ signal: options.signal,
1560
+ includeRaw
1561
+ })
1562
+ );
1563
+ if (compatMode !== "off") {
1564
+ stages.push(
1565
+ () => runWellKnownX402Stage({
1566
+ origin,
1567
+ fetcher,
1568
+ headers: options.headers,
1569
+ signal: options.signal,
1570
+ includeRaw
1571
+ })
1572
+ );
1573
+ stages.push(
1574
+ () => runDnsStage({
1575
+ origin,
1576
+ fetcher,
1577
+ txtResolver: options.txtResolver,
1578
+ headers: options.headers,
1579
+ signal: options.signal,
1580
+ includeRaw
1581
+ })
1582
+ );
1583
+ if (includeInteropMpp) {
1584
+ stages.push(
1585
+ () => runInteropMppStage({
1586
+ origin,
1587
+ fetcher,
1588
+ headers: options.headers,
1589
+ signal: options.signal,
1590
+ includeRaw
1591
+ })
1592
+ );
1593
+ }
1594
+ }
1595
+ stages.push(
1596
+ () => runProbeStage({
1597
+ origin,
1598
+ fetcher,
1599
+ headers: options.headers,
1600
+ signal: options.signal,
1601
+ probeCandidates: options.probeCandidates
1602
+ })
1603
+ );
1604
+ let selectedStage;
1605
+ for (const runStage of stages) {
1606
+ const startedAt = Date.now();
1607
+ const stageResult = await runStage();
1608
+ stageResult.warnings = applyStrictEscalation(stageResult.warnings, strictCompat);
1609
+ trace.push(toTrace(stageResult, startedAt));
1610
+ warnings.push(...stageResult.warnings);
1611
+ if (stageResult.resources.length > 0) {
1612
+ for (const stageWarning of stageResult.warnings) {
1613
+ if (stageWarning.resourceKey) {
1614
+ resourceWarnings[stageWarning.resourceKey] = [
1615
+ ...resourceWarnings[stageWarning.resourceKey] ?? [],
1616
+ stageWarning
1617
+ ];
1618
+ }
1619
+ }
1620
+ }
1621
+ if (!stageResult.valid) {
1622
+ continue;
1623
+ }
1624
+ const mergeWarnings = mergeResources(merged, stageResult.resources, resourceWarnings);
1625
+ warnings.push(...mergeWarnings);
1626
+ if (includeRaw && stageResult.raw !== void 0) {
1627
+ if (stageResult.stage === "openapi" || stageResult.stage === "override") {
1628
+ const rawObject = stageResult.raw;
1629
+ if (rawObject.openapi !== void 0) rawSources.openapi = rawObject.openapi;
1630
+ if (typeof rawObject.llmsTxt === "string") rawSources.llmsTxt = rawObject.llmsTxt;
1631
+ }
1632
+ if (stageResult.stage === "well-known/x402" || stageResult.stage === "override") {
1633
+ rawSources.wellKnownX402 = [...rawSources.wellKnownX402 ?? [], stageResult.raw];
1634
+ }
1635
+ if (stageResult.stage === "dns/_x402") {
1636
+ const rawObject = stageResult.raw;
1637
+ if (Array.isArray(rawObject.dnsRecords)) {
1638
+ rawSources.dnsRecords = rawObject.dnsRecords;
1639
+ }
1640
+ }
1641
+ if (stageResult.stage === "interop/mpp") {
1642
+ rawSources.interopMpp = stageResult.raw;
1643
+ }
1644
+ }
1645
+ if (!detailed) {
1646
+ selectedStage = selectedStage ?? stageResult.stage;
1647
+ break;
1648
+ }
1649
+ }
1650
+ if (merged.size === 0) {
1651
+ warnings.push(
1652
+ warning(
1653
+ "NO_DISCOVERY_SOURCES",
1654
+ "error",
1655
+ "No discovery stage returned first-valid-non-empty results."
1656
+ )
1657
+ );
1658
+ }
1659
+ const dedupedWarnings = dedupeWarnings(warnings);
1660
+ const upgradeSignal = computeUpgradeSignal(dedupedWarnings);
1661
+ const resources = [...merged.values()];
1662
+ if (!detailed) {
1663
+ return {
1664
+ origin,
1665
+ resources,
1666
+ warnings: dedupedWarnings,
1667
+ compatMode,
1668
+ ...upgradeSignal,
1669
+ ...selectedStage ? { selectedStage } : {}
1670
+ };
1671
+ }
1672
+ return {
1673
+ origin,
1674
+ resources,
1675
+ warnings: dedupedWarnings,
1676
+ compatMode,
1677
+ ...upgradeSignal,
1678
+ selectedStage: trace.find((entry) => entry.valid)?.stage,
1679
+ trace,
1680
+ resourceWarnings,
1681
+ ...includeRaw ? { rawSources } : {}
1682
+ };
1683
+ }
1684
+
1685
+ // src/index.ts
1686
+ async function discoverDetailed(options) {
1687
+ return await runDiscovery({ detailed: true, options });
1688
+ }
1689
+
1690
+ // src/cli/run.ts
1691
+ var CLI_USER_AGENT = "@agentcash/discovery-cli/0.1";
1692
+ function buildProbeCandidates(resources) {
1693
+ const methodsByPath = /* @__PURE__ */ new Map();
1694
+ for (const resource of resources) {
1695
+ const existing = methodsByPath.get(resource.path) ?? /* @__PURE__ */ new Set();
1696
+ existing.add(resource.method);
1697
+ methodsByPath.set(resource.path, existing);
1698
+ }
1699
+ return [...methodsByPath.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([path, methods]) => ({ path, methods: [...methods].sort() }));
1700
+ }
1701
+ function createTimeoutFetcher(timeoutMs, fetchImpl) {
1702
+ return async (input, init = {}) => {
1703
+ const controller = new AbortController();
1704
+ const onAbort = () => controller.abort();
1705
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1706
+ if (init.signal) {
1707
+ if (init.signal.aborted) {
1708
+ clearTimeout(timer);
1709
+ throw new Error("Request aborted");
1710
+ }
1711
+ init.signal.addEventListener("abort", onAbort, { once: true });
1712
+ }
1713
+ try {
1714
+ return await fetchImpl(input, {
1715
+ ...init,
1716
+ signal: controller.signal
1717
+ });
1718
+ } finally {
1719
+ clearTimeout(timer);
1720
+ init.signal?.removeEventListener("abort", onAbort);
1721
+ }
1722
+ };
1723
+ }
1724
+ async function runAudit(options, deps = {}) {
1725
+ const discoverDetailedFn = deps.discoverDetailedFn ?? discoverDetailed;
1726
+ const fetchImpl = deps.fetchImpl ?? fetch;
1727
+ const fetcher = createTimeoutFetcher(options.timeoutMs, fetchImpl);
1728
+ const baseOptions = {
1729
+ target: options.target,
1730
+ compatMode: options.compatMode,
1731
+ fetcher,
1732
+ headers: {
1733
+ "user-agent": CLI_USER_AGENT
1734
+ },
1735
+ rawView: "none"
1736
+ };
1737
+ const startedAt = Date.now();
1738
+ let result = await discoverDetailedFn(baseOptions);
1739
+ let probeCandidateCount = 0;
1740
+ if (options.probe) {
1741
+ const probeCandidates = buildProbeCandidates(result.resources);
1742
+ probeCandidateCount = probeCandidates.length;
1743
+ if (probeCandidates.length > 0) {
1744
+ result = await discoverDetailedFn({
1745
+ ...baseOptions,
1746
+ probeCandidates
1747
+ });
1748
+ }
1749
+ }
1750
+ return {
1751
+ result,
1752
+ durationMs: Date.now() - startedAt,
1753
+ probeCandidateCount
1754
+ };
1755
+ }
1756
+
1757
+ // src/cli.ts
1758
+ function defaultStdout(message) {
1759
+ process.stdout.write(`${message}
1760
+ `);
1761
+ }
1762
+ function defaultStderr(message) {
1763
+ process.stderr.write(`${message}
1764
+ `);
1765
+ }
1766
+ async function main(argv = process.argv.slice(2), deps = {}) {
1767
+ const stdout = deps.stdout ?? defaultStdout;
1768
+ const stderr = deps.stderr ?? defaultStderr;
1769
+ const parsed = parseCliArgs(argv);
1770
+ if (parsed.kind === "help") {
1771
+ stdout(renderHelp());
1772
+ return 0;
1773
+ }
1774
+ if (parsed.kind === "error") {
1775
+ stderr(parsed.message);
1776
+ stderr("");
1777
+ stderr(renderHelp());
1778
+ return 2;
1779
+ }
1780
+ try {
1781
+ const run = await runAudit(parsed.options, deps);
1782
+ if (parsed.options.json) {
1783
+ stdout(renderJson(run, parsed.options));
1784
+ } else if (parsed.options.verbose) {
1785
+ stdout(renderVerbose(run, parsed.options));
1786
+ } else {
1787
+ stdout(renderSummary(run, parsed.options));
1788
+ }
1789
+ return run.result.resources.length > 0 ? 0 : 1;
1790
+ } catch (error) {
1791
+ const message = error instanceof Error ? error.message : String(error);
1792
+ stderr(`Discovery audit failed: ${message}`);
1793
+ return 2;
1794
+ }
1795
+ }
1796
+ export {
1797
+ main
1798
+ };