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