@agentcash/discovery 0.1.4 → 1.0.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 CHANGED
@@ -1,622 +1,104 @@
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
- ];
1
+ // src/core/source/openapi/index.ts
2
+ import { okAsync, ResultAsync as ResultAsync2 } from "neverthrow";
12
3
 
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 parseClient(value) {
29
- if (value === "claude-code" || value === "skill-cli" || value === "generic-mcp" || value === "generic") {
30
- return value;
31
- }
32
- return null;
33
- }
34
- function parseContextWindowTokens(value) {
35
- const parsed = Number(value);
36
- if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
37
- return null;
38
- }
39
- return parsed;
40
- }
41
- function parseCliArgs(argv) {
42
- let target;
43
- let verbose = false;
44
- let json = false;
45
- let probe = false;
46
- let timeoutMs = DEFAULT_TIMEOUT_MS;
47
- let compatMode = DEFAULT_COMPAT_MODE;
48
- let color = true;
49
- let noTruncate = false;
50
- let harness = false;
51
- let client = "claude-code";
52
- let contextWindowTokens;
53
- for (let i = 0; i < argv.length; i += 1) {
54
- const arg = argv[i];
55
- if (arg === "--help" || arg === "-h") {
56
- return { kind: "help" };
57
- }
58
- if (arg === "--verbose" || arg === "-v") {
59
- verbose = true;
60
- continue;
61
- }
62
- if (arg === "--json") {
63
- json = true;
64
- continue;
65
- }
66
- if (arg === "--probe") {
67
- probe = true;
68
- continue;
69
- }
70
- if (arg === "--no-color") {
71
- color = false;
72
- continue;
73
- }
74
- if (arg === "--no-truncate") {
75
- noTruncate = true;
76
- continue;
77
- }
78
- if (arg === "--harness") {
79
- harness = true;
80
- continue;
81
- }
82
- if (arg === "--compat") {
83
- const value = argv[i + 1];
84
- if (!value) {
85
- return { kind: "error", message: "Missing value for --compat." };
86
- }
87
- const parsed = parseCompatibilityMode(value);
88
- if (!parsed) {
89
- return {
90
- kind: "error",
91
- message: `Invalid --compat value '${value}'. Expected: on | off | strict.`
92
- };
93
- }
94
- compatMode = parsed;
95
- i += 1;
96
- continue;
97
- }
98
- if (arg === "--timeout-ms") {
99
- const value = argv[i + 1];
100
- if (!value) {
101
- return { kind: "error", message: "Missing value for --timeout-ms." };
102
- }
103
- const parsed = parseTimeoutMs(value);
104
- if (!parsed) {
105
- return {
106
- kind: "error",
107
- message: `Invalid --timeout-ms value '${value}'. Expected a positive integer.`
108
- };
109
- }
110
- timeoutMs = parsed;
111
- i += 1;
112
- continue;
113
- }
114
- if (arg === "--client") {
115
- const value = argv[i + 1];
116
- if (!value) {
117
- return { kind: "error", message: "Missing value for --client." };
118
- }
119
- const parsed = parseClient(value);
120
- if (!parsed) {
121
- return {
122
- kind: "error",
123
- message: "Invalid --client value '" + value + "'. Expected: claude-code | skill-cli | generic-mcp | generic."
124
- };
125
- }
126
- client = parsed;
127
- i += 1;
128
- continue;
129
- }
130
- if (arg === "--context-window-tokens") {
131
- const value = argv[i + 1];
132
- if (!value) {
133
- return { kind: "error", message: "Missing value for --context-window-tokens." };
134
- }
135
- const parsed = parseContextWindowTokens(value);
136
- if (!parsed) {
137
- return {
138
- kind: "error",
139
- message: `Invalid --context-window-tokens value '${value}'. Expected a positive integer.`
140
- };
141
- }
142
- contextWindowTokens = parsed;
143
- i += 1;
144
- continue;
145
- }
146
- if (arg.startsWith("-")) {
147
- return { kind: "error", message: `Unknown flag '${arg}'.` };
148
- }
149
- if (target) {
150
- return {
151
- kind: "error",
152
- message: `Unexpected extra positional argument '${arg}'. Only one domain/URL is supported.`
153
- };
154
- }
155
- target = arg;
156
- }
157
- if (!target) {
158
- return { kind: "error", message: "Missing required <domain> argument." };
159
- }
160
- return {
161
- kind: "ok",
162
- options: {
163
- target,
164
- verbose,
165
- json,
166
- compatMode,
167
- probe,
168
- timeoutMs,
169
- color,
170
- noTruncate,
171
- harness,
172
- client,
173
- ...contextWindowTokens ? { contextWindowTokens } : {}
174
- }
175
- };
176
- }
177
- function renderHelp() {
178
- return [
179
- "Usage: npx @agentcash/discovery <domain-or-url> [options]",
180
- "",
181
- "Options:",
182
- " -v, --verbose Show full compatibility matrix",
183
- " --json Emit JSON output",
184
- " --compat <mode> Compatibility mode: on | off | strict (default: on)",
185
- " --probe Re-run with probe candidates from discovered paths",
186
- " --harness Render L0-L5 context harness audit view",
187
- " --client <id> Harness client: claude-code | skill-cli | generic-mcp | generic",
188
- " --context-window-tokens <n>",
189
- " Override client context window for harness budget checks",
190
- " --timeout-ms <ms> Per-request timeout in milliseconds (default: 5000)",
191
- " --no-color Disable ANSI colors",
192
- " --no-truncate Disable output truncation in verbose tables",
193
- " -h, --help Show help"
194
- ].join("\n");
195
- }
196
-
197
- // src/cli/render.ts
198
- import { getBorderCharacters, table } from "table";
199
- function createPalette(enabled) {
200
- const wrap = (code) => (value) => enabled ? `\x1B[${code}m${value}\x1B[0m` : value;
201
- return {
202
- dim: wrap(2),
203
- bold: wrap(1),
204
- red: wrap(31),
205
- green: wrap(32),
206
- yellow: wrap(33),
207
- cyan: wrap(36)
208
- };
209
- }
210
- function summarizeWarnings(warnings) {
211
- const errors = warnings.filter((entry) => entry.severity === "error").length;
212
- const warns = warnings.filter((entry) => entry.severity === "warn").length;
213
- const infos = warnings.filter((entry) => entry.severity === "info").length;
214
- const codeCounts = warnings.filter((entry) => entry.code !== "PROBE_STAGE_SKIPPED").reduce(
215
- (acc, entry) => {
216
- acc[entry.code] = (acc[entry.code] ?? 0) + 1;
217
- return acc;
218
- },
219
- {}
220
- );
221
- const codes = Object.entries(codeCounts).sort((a, b) => b[1] - a[1]).map(([code, count]) => ({ code, count }));
222
- return {
223
- total: warnings.length,
224
- errors,
225
- warns,
226
- infos,
227
- codes
228
- };
229
- }
230
- function asYesNo(value) {
231
- return value ? "yes" : "no";
232
- }
233
- function truncate(value, max, noTruncate) {
234
- if (noTruncate || value.length <= max) {
235
- return value;
236
- }
237
- if (max <= 1) return value.slice(0, max);
238
- return `${value.slice(0, Math.max(1, max - 1))}...`;
239
- }
240
- function formatPricing(resource) {
241
- if (resource.authHint !== "paid") return "-";
242
- if (resource.pricing) {
243
- if (resource.pricing.pricingMode === "fixed") {
244
- return resource.pricing.price ? `fixed ${resource.pricing.price}` : "fixed";
245
- }
246
- if (resource.pricing.pricingMode === "range") {
247
- return `range ${resource.pricing.minPrice ?? "?"}-${resource.pricing.maxPrice ?? "?"}`;
248
- }
249
- return "quote";
250
- }
251
- return resource.priceHint ? `hint ${resource.priceHint}` : "-";
252
- }
253
- function statusBadge(ok, palette) {
254
- return ok ? palette.green("[PASS]") : palette.red("[FAIL]");
255
- }
256
- function riskBadge(summary, palette) {
257
- if (summary.errors > 0) return palette.red("HIGH");
258
- if (summary.warns > 0) return palette.yellow("MEDIUM");
259
- return palette.green("LOW");
260
- }
261
- function sectionHeader(title, palette) {
262
- return `${palette.bold(title)}
263
- ${palette.dim("-".repeat(title.length))}`;
264
- }
265
- function introBlock(run, options, palette) {
266
- const summary = summarizeWarnings(run.result.warnings);
267
- const topCodes = summary.codes.slice(0, 3).map(({ code, count }) => `${code} (${count})`).join(", ");
268
- const lines = [
269
- `${palette.bold("AGENTCASH DISCOVERY AUDIT")} ${statusBadge(run.result.resources.length > 0, palette)}`,
270
- `${palette.dim("=".repeat(74))}`,
271
- `Target ${options.target}`,
272
- `Origin ${run.result.origin}`,
273
- `Stage ${run.result.selectedStage ?? "none"} Compat: ${run.result.compatMode}`,
274
- `Resources ${run.result.resources.length} Duration: ${run.durationMs}ms`,
275
- `Risk ${riskBadge(summary, palette)} Upgrade suggested: ${run.result.upgradeSuggested ? "yes" : "no"}`,
276
- `Warnings error=${summary.errors} warn=${summary.warns} info=${summary.infos}`,
277
- `Top signals ${topCodes || "-"}`
278
- ];
279
- return lines.join("\n");
280
- }
281
- function outcomeLine(run) {
282
- const selectedStage = run.result.selectedStage ?? "none";
283
- if (run.result.resources.length === 0) {
284
- return "No usable discovery metadata was found.";
285
- }
286
- if (selectedStage === "openapi" || selectedStage === "override") {
287
- return `Canonical discovery succeeded via ${selectedStage}.`;
288
- }
289
- return `Discovery succeeded, but only via legacy stage ${selectedStage}.`;
290
- }
291
- function actionItems(run, options) {
292
- const target = options.target;
293
- if (run.result.resources.length === 0) {
294
- return [
295
- `Run deeper diagnostics: npx @agentcash/discovery ${target} -v --probe --compat on`,
296
- `Check that ${target} serves /openapi.json or /.well-known/x402`,
297
- "If endpoint exists, verify it returns parseable JSON and non-empty resources"
298
- ];
299
- }
300
- if (run.result.selectedStage === "openapi" || run.result.selectedStage === "override") {
301
- return [
302
- `Run strict mode hardening: npx @agentcash/discovery ${target} --compat strict -v`,
303
- "Reduce warning count to near-zero and keep pricing/auth hints explicit"
304
- ];
305
- }
306
- return [
307
- "Publish canonical OpenAPI discovery metadata with x-payment-info and security schemes",
308
- `Validate migration path: npx @agentcash/discovery ${target} --compat strict -v --probe`,
309
- "Keep legacy well-known path only during sunset window"
310
- ];
311
- }
312
- function buildTable(data, maxColWidths, noTruncate) {
313
- const rendered = data.map(
314
- (row) => row.map((cell, idx) => {
315
- const width = maxColWidths[idx] ?? 120;
316
- return noTruncate ? cell : truncate(cell, width, false);
4
+ // src/schemas.ts
5
+ import { z } from "zod";
6
+ var OpenApiPaymentInfoSchema = z.object({
7
+ pricingMode: z.enum(["fixed", "range", "quote"]),
8
+ price: z.string().optional(),
9
+ minPrice: z.string().optional(),
10
+ maxPrice: z.string().optional(),
11
+ protocols: z.array(z.string()).optional()
12
+ });
13
+ var OpenApiOperationSchema = z.object({
14
+ operationId: z.string().optional(),
15
+ summary: z.string().optional(),
16
+ description: z.string().optional(),
17
+ tags: z.array(z.string()).optional(),
18
+ security: z.array(z.record(z.string(), z.array(z.string()))).optional(),
19
+ parameters: z.array(
20
+ z.object({
21
+ in: z.string(),
22
+ name: z.string(),
23
+ schema: z.unknown().optional(),
24
+ required: z.boolean().optional()
317
25
  })
318
- );
319
- return table(rendered, {
320
- border: getBorderCharacters("ramac"),
321
- columns: maxColWidths.reduce(
322
- (acc, width, idx) => {
323
- acc[idx] = noTruncate ? { wrapWord: false, alignment: "left" } : { width, truncate: width, wrapWord: false, alignment: "left" };
324
- return acc;
325
- },
326
- {}
327
- ),
328
- drawHorizontalLine: (index, size) => index === 0 || index === 1 || index === size,
329
- columnDefault: {
330
- paddingLeft: 1,
331
- paddingRight: 1
332
- }
333
- }).trimEnd();
334
- }
335
- function stageMatrixRows(result) {
336
- const header = ["stage", "valid", "resources", "duration", "selected", "warning codes"];
337
- const rows = result.trace.map((entry) => {
338
- const codes = [...new Set(entry.warnings.map((warning2) => warning2.code))].join(", ");
339
- return [
340
- entry.stage,
341
- asYesNo(entry.valid),
342
- String(entry.resourceCount),
343
- `${entry.durationMs}ms`,
344
- result.selectedStage === entry.stage ? "yes" : "-",
345
- codes || "-"
346
- ];
347
- });
348
- return [header, ...rows];
349
- }
350
- function resourceMatrixRows(result) {
351
- const sorted = [...result.resources].sort((a, b) => {
352
- if (a.path !== b.path) return a.path.localeCompare(b.path);
353
- if (a.method !== b.method) return a.method.localeCompare(b.method);
354
- return a.origin.localeCompare(b.origin);
355
- });
356
- const header = ["method", "path", "auth", "pricing", "source", "verified", "notes"];
357
- const rows = sorted.map((resource) => [
358
- resource.method,
359
- resource.path,
360
- resource.authHint ?? "-",
361
- formatPricing(resource),
362
- resource.source,
363
- asYesNo(resource.verified),
364
- resource.authHint === "siwx" ? "siwx" : "-"
365
- ]);
366
- return [header, ...rows];
367
- }
368
- function warningRows(summary, result) {
369
- const hintByCode = /* @__PURE__ */ new Map();
370
- for (const warning2 of result.warnings) {
371
- if (!hintByCode.has(warning2.code)) {
372
- hintByCode.set(warning2.code, warning2.hint ?? warning2.message);
373
- }
374
- }
375
- const header = ["severity", "code", "count", "hint/message"];
376
- const rows = summary.codes.slice(0, 8).map(({ code, count }) => {
377
- const sample = hintByCode.get(code) ?? "-";
378
- const severity = code.includes("NO_DISCOVERY") ? "error" : code.includes("LEGACY") ? "warn" : "info";
379
- return [severity, code, String(count), sample];
380
- });
381
- return [header, ...rows];
382
- }
383
- function harnessBudgetTable(report) {
384
- return [
385
- ["metric", "value"],
386
- ["client", `${report.client.id} (${report.client.surface})`],
387
- ["context window tokens", String(report.budget.contextWindowTokens)],
388
- ["zero-hop budget tokens", String(report.budget.zeroHopBudgetTokens)],
389
- ["estimated L0+L1 tokens", String(report.budget.estimatedZeroHopTokens)],
390
- ["within budget", report.budget.withinBudget ? "yes" : "no"]
391
- ];
392
- }
393
- function harnessLayerSummaryRows(report) {
394
- return [
395
- ["layer", "what", "primary command", "status"],
396
- [
397
- "L0",
398
- "trigger layer",
399
- report.levels.l0.installCommand,
400
- `${report.levels.l0.intentTriggers.length} triggers`
401
- ],
402
- [
403
- "L1",
404
- "installed domain index",
405
- report.levels.l1.fanoutCommands[0] ?? "-",
406
- `${report.levels.l1.domainClass}; stage=${report.levels.l1.selectedDiscoveryStage ?? "none"}`
407
- ],
408
- [
409
- "L2",
410
- "domain resources",
411
- report.levels.l2.command,
412
- `${report.levels.l2.resourceCount} resources`
413
- ],
414
- [
415
- "L3",
416
- "resource details",
417
- report.levels.l3.command,
418
- `${report.levels.l3.pricedResourceCount} priced`
419
- ],
420
- ["L4", "domain guidance", report.levels.l4.llmsTxtUrl ?? "-", report.levels.l4.guidanceStatus],
421
- ["L5", "cross-domain composition", "-", report.levels.l5.status]
422
- ];
423
- }
424
- function harnessL2PreviewRows(report, noTruncate) {
425
- const header = ["method", "path", "auth", "source"];
426
- const rows = report.levels.l2.resources.slice(0, 15).map((resource) => [
427
- resource.method,
428
- truncate(resource.path, 54, noTruncate),
429
- resource.authMode ?? "-",
430
- resource.source
431
- ]);
432
- return [header, ...rows];
433
- }
434
- function renderHarnessSummary(report, options, palette) {
435
- const lines = [];
436
- lines.push(
437
- `${palette.bold("L0-L5 Context Harness")} ${statusBadge(report.levels.l2.resourceCount > 0, palette)}`
438
- );
439
- lines.push(`${palette.dim("=".repeat(74))}`);
440
- lines.push(`Client ${report.client.label}`);
441
- lines.push(`Domain ${report.levels.l1.domain}`);
442
- lines.push(`L2 resources ${report.levels.l2.resourceCount}`);
443
- lines.push(
444
- `Budget ${report.budget.estimatedZeroHopTokens}/${report.budget.zeroHopBudgetTokens} tokens (L0+L1)`
445
- );
446
- lines.push("");
447
- lines.push("Layer map:");
448
- lines.push("1. L0 trigger surface -> install and route to agentcash");
449
- lines.push(`2. L1 domain index -> ${report.levels.l1.fanoutCommands[0]}`);
450
- lines.push(`3. L2 resources -> ${report.levels.l2.command}`);
451
- lines.push(`4. L3 details -> ${report.levels.l3.command}`);
452
- lines.push(
453
- `5. L4 guidance -> ${report.levels.l4.guidanceStatus}${report.levels.l4.llmsTxtUrl ? ` (${report.levels.l4.llmsTxtUrl})` : ""}`
454
- );
455
- lines.push("6. L5 cross-domain -> out of scope in v1");
456
- lines.push("");
457
- lines.push(
458
- `Next: npx @agentcash/discovery ${options.target} --harness -v --client ${report.client.id}`
459
- );
460
- return lines.join("\n");
26
+ ).optional(),
27
+ requestBody: z.object({
28
+ required: z.boolean().optional(),
29
+ content: z.record(z.string(), z.object({ schema: z.unknown().optional() }))
30
+ }).optional(),
31
+ responses: z.record(z.string(), z.unknown()).optional(),
32
+ "x-payment-info": OpenApiPaymentInfoSchema.optional()
33
+ });
34
+ var OpenApiPathItemSchema = z.object({
35
+ get: OpenApiOperationSchema.optional(),
36
+ post: OpenApiOperationSchema.optional(),
37
+ put: OpenApiOperationSchema.optional(),
38
+ delete: OpenApiOperationSchema.optional(),
39
+ patch: OpenApiOperationSchema.optional(),
40
+ head: OpenApiOperationSchema.optional(),
41
+ options: OpenApiOperationSchema.optional(),
42
+ trace: OpenApiOperationSchema.optional()
43
+ });
44
+ var OpenApiDocSchema = z.object({
45
+ // TODO(zdql): We should inherit a canonical OpenAPI schema and then extend with our types.
46
+ openapi: z.string(),
47
+ info: z.object({
48
+ title: z.string(),
49
+ version: z.string(),
50
+ description: z.string().optional(),
51
+ guidance: z.string().optional()
52
+ }),
53
+ servers: z.array(z.object({ url: z.string() })).optional(),
54
+ tags: z.array(z.object({ name: z.string() })).optional(),
55
+ components: z.object({ securitySchemes: z.record(z.string(), z.unknown()).optional() }).optional(),
56
+ "x-discovery": z.record(z.string(), z.unknown()).optional(),
57
+ paths: z.record(z.string(), OpenApiPathItemSchema)
58
+ });
59
+ var WellKnownDocSchema = z.object({
60
+ version: z.number().optional(),
61
+ resources: z.array(z.string()).default([]),
62
+ mppResources: z.array(z.string()).optional(),
63
+ // isMmmEnabled
64
+ description: z.string().optional(),
65
+ ownershipProofs: z.array(z.string()).optional(),
66
+ instructions: z.string().optional()
67
+ });
68
+ var WellKnownParsedSchema = z.object({
69
+ routes: z.array(
70
+ z.object({
71
+ path: z.string(),
72
+ method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"])
73
+ })
74
+ ),
75
+ instructions: z.string().optional()
76
+ });
77
+
78
+ // src/core/source/openapi/utils.ts
79
+ function isRecord(value) {
80
+ return value !== null && typeof value === "object" && !Array.isArray(value);
461
81
  }
462
- function renderHarnessVerbose(report, options, palette) {
463
- const lines = [];
464
- lines.push(
465
- `${palette.bold("L0-L5 Context Harness (Verbose)")} ${statusBadge(report.levels.l2.resourceCount > 0, palette)}`
466
- );
467
- lines.push(`${palette.dim("=".repeat(74))}`);
468
- lines.push(`Target ${report.target}`);
469
- lines.push(`Origin ${report.origin}`);
470
- lines.push(`Client ${report.client.label}`);
471
- lines.push("");
472
- lines.push(sectionHeader("Budget check", palette));
473
- lines.push(buildTable(harnessBudgetTable(report), [30, 42], options.noTruncate));
474
- lines.push("");
475
- lines.push(sectionHeader("Layer summary", palette));
476
- lines.push(buildTable(harnessLayerSummaryRows(report), [8, 24, 44, 28], options.noTruncate));
477
- lines.push("");
478
- lines.push(sectionHeader("L2 resource preview", palette));
479
- if (report.levels.l2.resources.length === 0) {
480
- lines.push(palette.dim("(no discovered resources)"));
481
- } else {
482
- lines.push(
483
- buildTable(
484
- harnessL2PreviewRows(report, options.noTruncate),
485
- [10, 54, 12, 20],
486
- options.noTruncate
487
- )
488
- );
489
- if (report.levels.l2.resources.length > 15) {
490
- lines.push(
491
- palette.dim(
492
- `Showing 15/${report.levels.l2.resources.length} resources. Use --json to inspect full L2/L3 payload.`
493
- )
494
- );
495
- }
496
- }
497
- lines.push("");
498
- lines.push(sectionHeader("L4 guidance", palette));
499
- lines.push(`status: ${report.levels.l4.guidanceStatus}`);
500
- lines.push(`llms.txt url: ${report.levels.l4.llmsTxtUrl ?? "-"}`);
501
- lines.push(`llms.txt tokens: ${report.levels.l4.llmsTxtTokenEstimate}`);
502
- lines.push(`preview: ${report.levels.l4.guidancePreview ?? "-"}`);
503
- lines.push("");
504
- lines.push(sectionHeader("Diagnostics", palette));
505
- lines.push(
506
- `warnings: ${report.diagnostics.warningCount} (errors=${report.diagnostics.errorWarningCount})`
507
- );
508
- lines.push(`selected stage: ${report.diagnostics.selectedStage ?? "none"}`);
509
- lines.push(`upgrade suggested: ${report.diagnostics.upgradeSuggested ? "yes" : "no"}`);
510
- lines.push(
511
- `upgrade reasons: ${report.diagnostics.upgradeReasons.length > 0 ? report.diagnostics.upgradeReasons.join(", ") : "-"}`
512
- );
513
- return lines.join("\n");
82
+ function hasSecurity(operation, scheme) {
83
+ return operation.security?.some((s) => scheme in s) ?? false;
514
84
  }
515
- function renderSummary(run, options) {
516
- const palette = createPalette(options.color);
517
- if (options.harness && run.harness) {
518
- return renderHarnessSummary(run.harness, options, palette);
519
- }
520
- const body = [];
521
- body.push(introBlock(run, options, palette));
522
- body.push("");
523
- body.push(`Outcome: ${outcomeLine(run)}`);
524
- const items = actionItems(run, options);
525
- body.push("Next:");
526
- for (let i = 0; i < items.length; i += 1) {
527
- body.push(`${i + 1}. ${items[i]}`);
528
- }
529
- if (options.probe) {
530
- body.push(`Probe candidates considered: ${run.probeCandidateCount}`);
531
- }
532
- return body.join("\n");
85
+ function has402Response(operation) {
86
+ return Boolean(operation.responses?.["402"]);
533
87
  }
534
- function renderVerbose(run, options) {
535
- const palette = createPalette(options.color);
536
- if (options.harness && run.harness) {
537
- return renderHarnessVerbose(run.harness, options, palette);
538
- }
539
- const summary = summarizeWarnings(run.result.warnings);
540
- const noTruncate = options.noTruncate;
541
- const body = [];
542
- body.push(introBlock(run, options, palette));
543
- body.push("");
544
- body.push(sectionHeader("What this means", palette));
545
- body.push(outcomeLine(run));
546
- body.push("");
547
- body.push(sectionHeader("Recommended actions", palette));
548
- const items = actionItems(run, options);
549
- for (let i = 0; i < items.length; i += 1) {
550
- body.push(`${i + 1}. ${items[i]}`);
551
- }
552
- body.push("");
553
- body.push(sectionHeader("Compatibility matrix", palette));
554
- body.push(buildTable(stageMatrixRows(run.result), [18, 8, 10, 10, 10, 56], noTruncate));
555
- body.push("");
556
- body.push(sectionHeader("Resource matrix", palette));
557
- if (run.result.resources.length === 0) {
558
- body.push(palette.dim("(no discovered resources)"));
559
- } else {
560
- const allRows = resourceMatrixRows(run.result);
561
- const previewRows = allRows.slice(0, 16);
562
- body.push(buildTable(previewRows, [8, 44, 10, 24, 18, 10, 10], noTruncate));
563
- if (allRows.length > previewRows.length) {
564
- body.push(
565
- palette.dim(
566
- `Showing ${previewRows.length - 1}/${allRows.length - 1} resources. Use --no-truncate or --json for complete output.`
567
- )
568
- );
569
- }
570
- }
571
- body.push("");
572
- body.push(sectionHeader("Warning breakdown", palette));
573
- if (summary.codes.length === 0) {
574
- body.push(palette.dim("(no warnings)"));
575
- } else {
576
- body.push(buildTable(warningRows(summary, run.result), [10, 34, 8, 62], noTruncate));
577
- }
578
- if (run.result.upgradeReasons.length > 0) {
579
- body.push("");
580
- body.push(sectionHeader("Upgrade guidance", palette));
581
- for (const reason of run.result.upgradeReasons) {
582
- body.push(`- ${reason}`);
583
- }
584
- }
585
- return body.join("\n");
586
- }
587
- function renderJson(run, options) {
588
- return JSON.stringify(
589
- {
590
- ok: run.result.resources.length > 0,
591
- target: options.target,
592
- origin: run.result.origin,
593
- compatMode: run.result.compatMode,
594
- selectedStage: run.result.selectedStage ?? null,
595
- resources: run.result.resources,
596
- trace: run.result.trace,
597
- warnings: run.result.warnings,
598
- upgradeSuggested: run.result.upgradeSuggested,
599
- upgradeReasons: run.result.upgradeReasons,
600
- meta: {
601
- durationMs: run.durationMs,
602
- probeEnabled: options.probe,
603
- probeCandidateCount: run.probeCandidateCount,
604
- timeoutMs: options.timeoutMs
605
- },
606
- ...run.harness ? { harness: run.harness } : {}
607
- },
608
- null,
609
- 2
610
- );
88
+ function inferAuthMode(operation) {
89
+ const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
90
+ const hasPayment = hasXPaymentInfo || has402Response(operation);
91
+ const hasApiKey = hasSecurity(operation, "apiKey");
92
+ const hasSiwx = hasSecurity(operation, "siwx");
93
+ if (hasPayment && hasApiKey) return "apiKey+paid";
94
+ if (hasXPaymentInfo) return "paid";
95
+ if (hasPayment) return hasSiwx ? "siwx" : "paid";
96
+ if (hasApiKey) return "apiKey";
97
+ if (hasSiwx) return "siwx";
98
+ return void 0;
611
99
  }
612
100
 
613
- // src/mmm-enabled.ts
614
- var isMmmEnabled = () => "0.1.4".includes("-mmm");
615
-
616
- // src/core/constants.ts
617
- var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
618
- var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
619
- var LLMS_TOKEN_WARNING_THRESHOLD = 2500;
101
+ // src/core/lib/constants.ts
620
102
  var HTTP_METHODS = /* @__PURE__ */ new Set([
621
103
  "GET",
622
104
  "POST",
@@ -627,10 +109,9 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
627
109
  "OPTIONS",
628
110
  "TRACE"
629
111
  ]);
630
- var DEFAULT_PROBE_METHODS = ["GET", "POST"];
631
112
  var DEFAULT_MISSING_METHOD = "POST";
632
113
 
633
- // src/core/url.ts
114
+ // src/core/lib/url.ts
634
115
  function normalizeOrigin(target) {
635
116
  const trimmed = target.trim();
636
117
  const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
@@ -648,9 +129,6 @@ function normalizePath(pathname) {
648
129
  const normalized = prefixed.replace(/\/+/g, "/");
649
130
  return normalized !== "/" ? normalized.replace(/\/$/, "") : "/";
650
131
  }
651
- function toResourceKey(origin, method, path) {
652
- return `${origin} ${method} ${normalizePath(path)}`;
653
- }
654
132
  function parseMethod(value) {
655
133
  if (!value) return void 0;
656
134
  const upper = value.toUpperCase();
@@ -665,901 +143,504 @@ function toAbsoluteUrl(origin, value) {
665
143
  }
666
144
  }
667
145
 
668
- // src/core/warnings.ts
669
- function warning(code, severity, message, options) {
670
- return {
671
- code,
672
- severity,
673
- message,
674
- ...options?.hint ? { hint: options.hint } : {},
675
- ...options?.stage ? { stage: options.stage } : {},
676
- ...options?.resourceKey ? { resourceKey: options.resourceKey } : {}
677
- };
678
- }
679
- function applyStrictEscalation(warnings, strict) {
680
- if (!strict) return warnings;
681
- return warnings.map((entry) => {
682
- if (!STRICT_ESCALATION_CODES.includes(entry.code)) {
683
- return entry;
684
- }
685
- if (entry.severity === "error") return entry;
686
- return {
687
- ...entry,
688
- severity: "error",
689
- message: `${entry.message} (strict mode escalated)`
690
- };
691
- });
146
+ // src/core/source/fetch.ts
147
+ import { ResultAsync } from "neverthrow";
148
+ function toFetchError(err) {
149
+ const cause = err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError") ? "timeout" : "network";
150
+ return { cause, message: String(err) };
692
151
  }
693
- function dedupeWarnings(warnings) {
694
- const seen = /* @__PURE__ */ new Set();
695
- const output = [];
696
- for (const item of warnings) {
697
- const key = `${item.code}|${item.severity}|${item.stage ?? ""}|${item.resourceKey ?? ""}|${item.message}`;
698
- if (seen.has(key)) continue;
699
- seen.add(key);
700
- output.push(item);
701
- }
702
- return output;
152
+ function fetchSafe(url, init) {
153
+ return ResultAsync.fromPromise(fetch(url, init), toFetchError);
703
154
  }
704
155
 
705
- // src/core/normalize.ts
706
- function createResource(input, confidence, trustTier) {
707
- const normalizedOrigin = normalizeOrigin(input.origin);
708
- const normalizedPath = normalizePath(input.path);
709
- return {
710
- resourceKey: toResourceKey(normalizedOrigin, input.method, normalizedPath),
711
- origin: normalizedOrigin,
712
- method: input.method,
713
- path: normalizedPath,
714
- source: input.source,
715
- verified: false,
716
- ...input.protocolHints?.length ? { protocolHints: [...new Set(input.protocolHints)] } : {},
717
- ...input.priceHint ? { priceHint: input.priceHint } : {},
718
- ...input.pricing ? { pricing: input.pricing } : {},
719
- ...input.authHint ? { authHint: input.authHint } : {},
720
- ...input.summary ? { summary: input.summary } : {},
721
- confidence,
722
- trustTier,
723
- ...input.links ? { links: input.links } : {}
724
- };
725
- }
156
+ // src/mmm-enabled.ts
157
+ var isMmmEnabled = () => "1.0.0".includes("-mmm");
726
158
 
727
- // src/compat/legacy-x402scan/wellKnown.ts
728
- function isRecord(value) {
729
- return value !== null && typeof value === "object" && !Array.isArray(value);
730
- }
731
- function parseLegacyResourceEntry(entry) {
732
- const trimmed = entry.trim();
733
- if (!trimmed) return null;
734
- const parts = trimmed.split(/\s+/);
735
- if (parts.length >= 2) {
736
- const maybeMethod = parseMethod(parts[0]);
737
- if (maybeMethod) {
738
- const target = parts.slice(1).join(" ");
739
- return { method: maybeMethod, target };
740
- }
741
- }
742
- if (trimmed.startsWith("/") || /^https?:\/\//i.test(trimmed)) {
743
- return { target: trimmed };
744
- }
745
- return null;
746
- }
747
- function parseWellKnownPayload(payload, origin, sourceUrl) {
748
- const warnings = [
749
- warning(
750
- "LEGACY_WELL_KNOWN_USED",
751
- "warn",
752
- "Using legacy /.well-known/x402 compatibility path. Migrate to OpenAPI-first.",
753
- {
754
- stage: "well-known/x402"
755
- }
756
- )
757
- ];
758
- if (!isRecord(payload)) {
759
- warnings.push(
760
- warning("PARSE_FAILED", "error", "Legacy well-known payload is not an object", {
761
- stage: "well-known/x402"
762
- })
763
- );
764
- return { resources: [], warnings, raw: payload };
765
- }
766
- const resourcesRaw = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
767
- if (resourcesRaw.length === 0) {
768
- warnings.push(
769
- warning("STAGE_EMPTY", "warn", "Legacy well-known has no valid resources array", {
770
- stage: "well-known/x402"
771
- })
772
- );
773
- }
774
- const instructions = typeof payload.instructions === "string" ? payload.instructions : void 0;
775
- const ownershipProofs = Array.isArray(payload.ownershipProofs) ? payload.ownershipProofs.filter((entry) => typeof entry === "string") : [];
776
- if (instructions) {
777
- warnings.push(
778
- warning(
779
- "LEGACY_INSTRUCTIONS_USED",
780
- "warn",
781
- "Using /.well-known/x402.instructions as compatibility guidance fallback. Prefer llms.txt.",
782
- {
783
- stage: "well-known/x402",
784
- hint: "Move guidance to llms.txt and reference via x-discovery.llmsTxtUrl in OpenAPI."
785
- }
786
- )
787
- );
788
- }
789
- if (ownershipProofs.length > 0) {
790
- warnings.push(
791
- warning(
792
- "LEGACY_OWNERSHIP_PROOFS_USED",
793
- "warn",
794
- "Using /.well-known/x402.ownershipProofs compatibility field. Prefer OpenAPI provenance extension.",
795
- {
796
- stage: "well-known/x402",
797
- hint: "Move ownership proofs to x-discovery.ownershipProofs in OpenAPI."
798
- }
799
- )
800
- );
801
- }
802
- const resources = [];
803
- for (const rawEntry of resourcesRaw) {
804
- const parsed = parseLegacyResourceEntry(rawEntry);
805
- if (!parsed) {
806
- warnings.push(
807
- warning("PARSE_FAILED", "warn", `Invalid legacy resource entry: ${rawEntry}`, {
808
- stage: "well-known/x402"
809
- })
810
- );
811
- continue;
812
- }
813
- const absolute = toAbsoluteUrl(origin, parsed.target);
814
- if (!absolute) {
815
- warnings.push(
816
- warning("PARSE_FAILED", "warn", `Invalid legacy resource URL: ${rawEntry}`, {
817
- stage: "well-known/x402"
818
- })
819
- );
820
- continue;
821
- }
822
- const method = parsed.method ?? DEFAULT_MISSING_METHOD;
823
- if (!parsed.method) {
824
- warnings.push(
825
- warning(
826
- "LEGACY_MISSING_METHOD",
827
- "warn",
828
- `Legacy resource '${rawEntry}' missing method. Defaulting to ${DEFAULT_MISSING_METHOD}.`,
829
- {
830
- stage: "well-known/x402"
831
- }
832
- )
159
+ // src/core/source/openapi/index.ts
160
+ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
161
+ const routes = [];
162
+ for (const [rawPath, pathItem] of Object.entries(doc.paths)) {
163
+ for (const httpMethod of [...HTTP_METHODS]) {
164
+ const operation = pathItem[httpMethod.toLowerCase()];
165
+ if (!operation) continue;
166
+ const authMode = inferAuthMode(operation) ?? void 0;
167
+ if (!authMode) continue;
168
+ const p = operation["x-payment-info"];
169
+ const protocols = (p?.protocols ?? []).filter(
170
+ (proto) => proto !== "mpp" || isMmmEnabled()
833
171
  );
172
+ if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) continue;
173
+ if (authMode === "paid" && protocols.length === 0) continue;
174
+ const pricing = authMode === "paid" && p ? {
175
+ pricingMode: p.pricingMode,
176
+ ...p.price ? { price: p.price } : {},
177
+ ...p.minPrice ? { minPrice: p.minPrice } : {},
178
+ ...p.maxPrice ? { maxPrice: p.maxPrice } : {}
179
+ } : void 0;
180
+ const summary = operation.summary ?? operation.description;
181
+ routes.push({
182
+ path: normalizePath(rawPath),
183
+ method: httpMethod.toUpperCase(),
184
+ ...summary ? { summary } : {},
185
+ authMode,
186
+ ...protocols.length ? { protocols } : {},
187
+ ...pricing ? { pricing } : {}
188
+ });
834
189
  }
835
- const path = normalizePath(absolute.pathname);
836
- resources.push(
837
- createResource(
838
- {
839
- origin: absolute.origin,
840
- method,
841
- path,
842
- source: "well-known/x402",
843
- summary: `${method} ${path}`,
844
- authHint: "paid",
845
- protocolHints: ["x402"],
846
- links: {
847
- wellKnownUrl: sourceUrl,
848
- discoveryUrl: sourceUrl
849
- }
850
- },
851
- 0.65,
852
- ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
853
- )
854
- );
855
190
  }
856
191
  return {
857
- resources,
858
- warnings,
859
- raw: payload
192
+ info: {
193
+ title: doc.info.title,
194
+ ...doc.info.description ? { description: doc.info.description } : {},
195
+ version: doc.info.version
196
+ },
197
+ routes,
198
+ ...doc.info.guidance ? { guidance: doc.info.guidance } : {}
860
199
  };
861
- }
862
- async function runWellKnownX402Stage(options) {
863
- const stageUrl = options.url ?? `${options.origin}/.well-known/x402`;
200
+ });
201
+ async function parseBody(response, url) {
864
202
  try {
865
- const response = await options.fetcher(stageUrl, {
866
- method: "GET",
867
- headers: { Accept: "application/json", ...options.headers },
868
- signal: options.signal
869
- });
870
- if (!response.ok) {
871
- if (response.status === 404) {
872
- return {
873
- stage: "well-known/x402",
874
- valid: false,
875
- resources: [],
876
- warnings: [],
877
- links: { wellKnownUrl: stageUrl }
878
- };
879
- }
880
- return {
881
- stage: "well-known/x402",
882
- valid: false,
883
- resources: [],
884
- warnings: [
885
- warning("FETCH_FAILED", "warn", `Legacy well-known fetch failed (${response.status})`, {
886
- stage: "well-known/x402"
887
- })
888
- ],
889
- links: { wellKnownUrl: stageUrl }
890
- };
891
- }
892
203
  const payload = await response.json();
893
- const parsed = parseWellKnownPayload(payload, options.origin, stageUrl);
894
- return {
895
- stage: "well-known/x402",
896
- valid: parsed.resources.length > 0,
897
- resources: parsed.resources,
898
- warnings: parsed.warnings,
899
- links: { wellKnownUrl: stageUrl },
900
- ...options.includeRaw ? { raw: parsed.raw } : {}
901
- };
902
- } catch (error) {
903
- return {
904
- stage: "well-known/x402",
905
- valid: false,
906
- resources: [],
907
- warnings: [
908
- warning(
909
- "FETCH_FAILED",
910
- "warn",
911
- `Legacy well-known fetch exception: ${error instanceof Error ? error.message : String(error)}`,
912
- {
913
- stage: "well-known/x402"
914
- }
915
- )
916
- ],
917
- links: { wellKnownUrl: stageUrl }
918
- };
204
+ const parsed = OpenApiParsedSchema.safeParse(payload);
205
+ if (!parsed.success) return null;
206
+ return { raw: payload, ...parsed.data, fetchedUrl: url };
207
+ } catch {
208
+ return null;
919
209
  }
920
210
  }
921
-
922
- // src/compat/legacy-x402scan/dns.ts
923
- function parseDnsRecord(record) {
924
- const trimmed = record.trim();
925
- if (!trimmed) return null;
926
- if (/^https?:\/\//i.test(trimmed)) {
927
- return { url: trimmed, legacyPlainUrl: true };
928
- }
929
- const parts = trimmed.split(";").map((entry) => entry.trim()).filter(Boolean);
930
- const keyValues = /* @__PURE__ */ new Map();
931
- for (const part of parts) {
932
- const separator = part.indexOf("=");
933
- if (separator <= 0) continue;
934
- const key = part.slice(0, separator).trim().toLowerCase();
935
- const value = part.slice(separator + 1).trim();
936
- if (key && value) keyValues.set(key, value);
937
- }
938
- if (keyValues.get("v") !== "x4021") return null;
939
- const url = keyValues.get("url");
940
- if (!url || !/^https?:\/\//i.test(url)) return null;
941
- return { url, legacyPlainUrl: false };
211
+ function getOpenAPI(origin, headers, signal, specificationOverrideUrl) {
212
+ const url = specificationOverrideUrl ?? `${origin}/openapi.json`;
213
+ return fetchSafe(url, {
214
+ method: "GET",
215
+ headers: { Accept: "application/json", ...headers },
216
+ signal
217
+ }).andThen((response) => {
218
+ if (!response.ok) return okAsync(null);
219
+ return ResultAsync2.fromSafePromise(parseBody(response, url));
220
+ });
942
221
  }
943
- async function runDnsStage(options) {
944
- if (!options.txtResolver) {
945
- return {
946
- stage: "dns/_x402",
947
- valid: false,
948
- resources: [],
949
- warnings: []
950
- };
951
- }
952
- const origin = normalizeOrigin(options.origin);
953
- const hostname = new URL(origin).hostname;
954
- const fqdn = `_x402.${hostname}`;
955
- let records;
956
- try {
957
- records = await options.txtResolver(fqdn);
958
- } catch (error) {
959
- return {
960
- stage: "dns/_x402",
961
- valid: false,
962
- resources: [],
963
- warnings: [
964
- warning(
965
- "FETCH_FAILED",
966
- "warn",
967
- `DNS TXT lookup failed for ${fqdn}: ${error instanceof Error ? error.message : String(error)}`,
968
- { stage: "dns/_x402" }
969
- )
970
- ]
971
- };
972
- }
973
- if (records.length === 0) {
974
- return {
975
- stage: "dns/_x402",
976
- valid: false,
977
- resources: [],
978
- warnings: []
979
- };
980
- }
981
- const warnings = [
982
- warning(
983
- "LEGACY_DNS_USED",
984
- "warn",
985
- "Using DNS _x402 compatibility path. Migrate to OpenAPI-first discovery.",
222
+
223
+ // src/core/source/wellknown/index.ts
224
+ import { okAsync as okAsync2, ResultAsync as ResultAsync3 } from "neverthrow";
225
+ function toWellKnownParsed(origin, doc) {
226
+ const routes = doc.resources.flatMap((entry) => {
227
+ const trimmed = entry.trim();
228
+ if (!trimmed) return [];
229
+ const parts = trimmed.split(/\s+/);
230
+ const maybeMethod = parts.length >= 2 ? parseMethod(parts[0]) : void 0;
231
+ const target = maybeMethod ? parts.slice(1).join(" ") : trimmed;
232
+ const absolute = toAbsoluteUrl(origin, target);
233
+ if (!absolute) return [];
234
+ return [
986
235
  {
987
- stage: "dns/_x402"
236
+ path: normalizePath(absolute.pathname),
237
+ method: maybeMethod ?? DEFAULT_MISSING_METHOD
988
238
  }
989
- )
990
- ];
991
- const urls = [];
992
- for (const record of records) {
993
- const parsed = parseDnsRecord(record);
994
- if (!parsed) {
995
- warnings.push(
996
- warning("PARSE_FAILED", "warn", `Invalid DNS _x402 TXT record: ${record}`, {
997
- stage: "dns/_x402"
998
- })
999
- );
1000
- continue;
1001
- }
1002
- urls.push(parsed.url);
1003
- if (parsed.legacyPlainUrl) {
1004
- warnings.push(
1005
- warning("LEGACY_DNS_PLAIN_URL", "warn", `Legacy plain URL TXT format used: ${record}`, {
1006
- stage: "dns/_x402",
1007
- hint: "Use v=x4021;url=<https-url> format."
1008
- })
1009
- );
1010
- }
1011
- }
1012
- if (urls.length === 0) {
1013
- return {
1014
- stage: "dns/_x402",
1015
- valid: false,
1016
- resources: [],
1017
- warnings,
1018
- ...options.includeRaw ? { raw: { dnsRecords: records } } : {}
1019
- };
1020
- }
1021
- const mergedWarnings = [...warnings];
1022
- const mergedResources = [];
1023
- const rawDocuments = [];
1024
- for (const url of urls) {
1025
- const stageResult = await runWellKnownX402Stage({
1026
- origin,
1027
- url,
1028
- fetcher: options.fetcher,
1029
- headers: options.headers,
1030
- signal: options.signal,
1031
- includeRaw: options.includeRaw
1032
- });
1033
- mergedResources.push(...stageResult.resources);
1034
- mergedWarnings.push(...stageResult.warnings);
1035
- if (options.includeRaw && stageResult.raw !== void 0) {
1036
- rawDocuments.push({ url, document: stageResult.raw });
1037
- }
1038
- }
1039
- const deduped = /* @__PURE__ */ new Map();
1040
- for (const resource of mergedResources) {
1041
- if (!deduped.has(resource.resourceKey)) {
1042
- deduped.set(resource.resourceKey, {
1043
- ...resource,
1044
- source: "dns/_x402",
1045
- links: {
1046
- ...resource.links,
1047
- discoveryUrl: resource.links?.discoveryUrl
1048
- }
1049
- });
1050
- }
1051
- }
1052
- return {
1053
- stage: "dns/_x402",
1054
- valid: deduped.size > 0,
1055
- resources: [...deduped.values()],
1056
- warnings: mergedWarnings,
1057
- ...options.includeRaw ? { raw: { dnsRecords: records, documents: rawDocuments } } : {}
1058
- };
1059
- }
1060
-
1061
- // src/compat/interop-mpp/wellKnownMpp.ts
1062
- function isRecord2(value) {
1063
- return value !== null && typeof value === "object" && !Array.isArray(value);
239
+ ];
240
+ });
241
+ return { routes, ...doc.instructions ? { instructions: doc.instructions } : {} };
1064
242
  }
1065
- async function runInteropMppStage(options) {
1066
- const url = `${options.origin}${WELL_KNOWN_MPP_PATH}`;
243
+ async function parseBody2(response, origin, url) {
1067
244
  try {
1068
- const response = await options.fetcher(url, {
1069
- method: "GET",
1070
- headers: { Accept: "application/json", ...options.headers },
1071
- signal: options.signal
1072
- });
1073
- if (!response.ok) {
1074
- return {
1075
- stage: "interop/mpp",
1076
- valid: false,
1077
- resources: [],
1078
- warnings: []
1079
- };
1080
- }
1081
245
  const payload = await response.json();
1082
- if (!isRecord2(payload)) {
1083
- return {
1084
- stage: "interop/mpp",
1085
- valid: false,
1086
- resources: [],
1087
- warnings: [
1088
- warning("PARSE_FAILED", "warn", "Interop /.well-known/mpp payload is not an object", {
1089
- stage: "interop/mpp"
1090
- })
1091
- ],
1092
- ...options.includeRaw ? { raw: payload } : {}
1093
- };
1094
- }
1095
- const warnings = [
1096
- warning(
1097
- "INTEROP_MPP_USED",
1098
- "info",
1099
- "Using /.well-known/mpp interop adapter. This is additive and non-canonical.",
1100
- {
1101
- stage: "interop/mpp",
1102
- hint: "Use for registry indexing only, not canonical runtime routing."
1103
- }
1104
- )
1105
- ];
1106
- const resources = [];
1107
- const fromStringResources = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
1108
- for (const entry of fromStringResources) {
1109
- const parts = entry.trim().split(/\s+/);
1110
- let method = parseMethod(parts[0]);
1111
- let path = parts.join(" ");
1112
- if (method) {
1113
- path = parts.slice(1).join(" ");
1114
- } else {
1115
- method = "POST";
1116
- }
1117
- const normalizedPath = normalizePath(path);
1118
- resources.push(
1119
- createResource(
1120
- {
1121
- origin: options.origin,
1122
- method,
1123
- path: normalizedPath,
1124
- source: "interop/mpp",
1125
- summary: `${method} ${normalizedPath}`,
1126
- authHint: "paid",
1127
- protocolHints: ["mpp"],
1128
- links: { discoveryUrl: url }
1129
- },
1130
- 0.45,
1131
- "unverified"
1132
- )
1133
- );
1134
- }
1135
- const fromServiceObjects = Array.isArray(payload.services) ? payload.services.filter((entry) => isRecord2(entry)) : [];
1136
- for (const service of fromServiceObjects) {
1137
- const path = typeof service.path === "string" ? service.path : void 0;
1138
- const method = parseMethod(typeof service.method === "string" ? service.method : void 0) ?? "POST";
1139
- if (!path) continue;
1140
- const normalizedPath = normalizePath(path);
1141
- resources.push(
1142
- createResource(
1143
- {
1144
- origin: options.origin,
1145
- method,
1146
- path: normalizedPath,
1147
- source: "interop/mpp",
1148
- summary: typeof service.summary === "string" ? service.summary : `${method} ${normalizedPath}`,
1149
- authHint: "paid",
1150
- protocolHints: ["mpp"],
1151
- links: { discoveryUrl: url }
1152
- },
1153
- 0.45,
1154
- "unverified"
1155
- )
1156
- );
1157
- }
1158
- const deduped = /* @__PURE__ */ new Map();
1159
- for (const resource of resources) {
1160
- if (!deduped.has(resource.resourceKey)) deduped.set(resource.resourceKey, resource);
1161
- }
1162
- return {
1163
- stage: "interop/mpp",
1164
- valid: deduped.size > 0,
1165
- resources: [...deduped.values()],
1166
- warnings,
1167
- ...options.includeRaw ? { raw: payload } : {}
1168
- };
1169
- } catch (error) {
1170
- return {
1171
- stage: "interop/mpp",
1172
- valid: false,
1173
- resources: [],
1174
- warnings: [
1175
- warning(
1176
- "FETCH_FAILED",
1177
- "warn",
1178
- `Interop /.well-known/mpp fetch failed: ${error instanceof Error ? error.message : String(error)}`,
1179
- {
1180
- stage: "interop/mpp"
1181
- }
1182
- )
1183
- ]
1184
- };
246
+ const doc = WellKnownDocSchema.safeParse(payload);
247
+ if (!doc.success) return null;
248
+ const parsed = WellKnownParsedSchema.safeParse(toWellKnownParsed(origin, doc.data));
249
+ if (!parsed.success) return null;
250
+ return { raw: payload, ...parsed.data, fetchedUrl: url };
251
+ } catch {
252
+ return null;
1185
253
  }
1186
254
  }
1187
-
1188
- // src/core/token.ts
1189
- function estimateTokenCount(text) {
1190
- return Math.ceil(text.length / 4);
255
+ function getWellKnown(origin, headers, signal) {
256
+ const url = `${origin}/.well-known/x402`;
257
+ return fetchSafe(url, {
258
+ method: "GET",
259
+ headers: { Accept: "application/json", ...headers },
260
+ signal
261
+ }).andThen((response) => {
262
+ if (!response.ok) return okAsync2(null);
263
+ return ResultAsync3.fromSafePromise(parseBody2(response, origin, url));
264
+ });
1191
265
  }
1192
266
 
1193
- // src/core/openapi-utils.ts
1194
- function isRecord3(value) {
1195
- return value !== null && typeof value === "object" && !Array.isArray(value);
267
+ // src/core/layers/l2.ts
268
+ function formatPrice(pricing) {
269
+ if (pricing.pricingMode === "fixed") return `$${pricing.price}`;
270
+ if (pricing.pricingMode === "range") return `$${pricing.minPrice}-$${pricing.maxPrice}`;
271
+ return pricing.maxPrice ? `up to $${pricing.maxPrice}` : "quote";
272
+ }
273
+ function checkL2ForOpenAPI(openApi) {
274
+ const routes = openApi.routes.map((route) => ({
275
+ path: route.path,
276
+ method: route.method,
277
+ summary: route.summary ?? `${route.method} ${route.path}`,
278
+ ...route.authMode ? { authMode: route.authMode } : {},
279
+ ...route.pricing ? { price: formatPrice(route.pricing) } : {},
280
+ ...route.protocols?.length ? { protocols: route.protocols } : {}
281
+ }));
282
+ return {
283
+ ...openApi.info.title ? { title: openApi.info.title } : {},
284
+ ...openApi.info.description ? { description: openApi.info.description } : {},
285
+ routes,
286
+ source: "openapi"
287
+ };
1196
288
  }
1197
- function hasSecurity(operation, scheme) {
1198
- const security = operation.security;
1199
- return Array.isArray(security) && security.some((s) => isRecord3(s) && scheme in s);
289
+ function checkL2ForWellknown(wellKnown) {
290
+ const routes = wellKnown.routes.map((route) => ({
291
+ path: route.path,
292
+ method: route.method,
293
+ summary: `${route.method} ${route.path}`,
294
+ authMode: "paid",
295
+ protocols: ["x402"]
296
+ }));
297
+ return { routes, source: "well-known/x402" };
1200
298
  }
1201
- function has402Response(operation) {
1202
- const responses = operation.responses;
1203
- if (!isRecord3(responses)) return false;
1204
- return Boolean(responses["402"]);
299
+
300
+ // src/core/layers/l4.ts
301
+ function checkL4ForOpenAPI(openApi) {
302
+ if (openApi.guidance) {
303
+ return { guidance: openApi.guidance, source: "openapi" };
304
+ }
305
+ return null;
1205
306
  }
1206
- function inferAuthMode(operation) {
1207
- if (isRecord3(operation["x-payment-info"])) return "paid";
1208
- if (has402Response(operation)) {
1209
- if (hasSecurity(operation, "siwx")) return "siwx";
1210
- return "paid";
307
+ function checkL4ForWellknown(wellKnown) {
308
+ if (wellKnown.instructions) {
309
+ return { guidance: wellKnown.instructions, source: "well-known/x402" };
1211
310
  }
1212
- if (hasSecurity(operation, "apiKey")) return "apiKey";
1213
- if (hasSecurity(operation, "siwx")) return "siwx";
1214
- return void 0;
311
+ return null;
1215
312
  }
1216
313
 
1217
- // src/core/openapi.ts
1218
- function asString(value) {
1219
- return typeof value === "string" && value.length > 0 ? value : void 0;
1220
- }
1221
- function parsePriceValue(value) {
1222
- if (typeof value === "string" && value.length > 0) return value;
1223
- if (typeof value === "number" && Number.isFinite(value)) return String(value);
1224
- return void 0;
314
+ // src/audit/codes.ts
315
+ var AUDIT_CODES = {
316
+ // ─── Source presence ─────────────────────────────────────────────────────────
317
+ OPENAPI_NOT_FOUND: "OPENAPI_NOT_FOUND",
318
+ WELLKNOWN_NOT_FOUND: "WELLKNOWN_NOT_FOUND",
319
+ // ─── OpenAPI quality ─────────────────────────────────────────────────────────
320
+ OPENAPI_NO_ROUTES: "OPENAPI_NO_ROUTES",
321
+ // ─── L2 route-list checks ────────────────────────────────────────────────────
322
+ L2_NO_ROUTES: "L2_NO_ROUTES",
323
+ L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH",
324
+ L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING",
325
+ L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID",
326
+ L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID",
327
+ // ─── L3 endpoint advisory checks ─────────────────────────────────────────────
328
+ L3_NOT_FOUND: "L3_NOT_FOUND",
329
+ L3_INPUT_SCHEMA_MISSING: "L3_INPUT_SCHEMA_MISSING",
330
+ L3_AUTH_MODE_MISSING: "L3_AUTH_MODE_MISSING",
331
+ L3_PROTOCOLS_MISSING_ON_PAID: "L3_PROTOCOLS_MISSING_ON_PAID",
332
+ // ─── L4 guidance checks ──────────────────────────────────────────────────────
333
+ L4_GUIDANCE_MISSING: "L4_GUIDANCE_MISSING",
334
+ L4_GUIDANCE_TOO_LONG: "L4_GUIDANCE_TOO_LONG"
335
+ };
336
+
337
+ // src/audit/warnings.ts
338
+ var GUIDANCE_TOO_LONG_CHARS = 4e3;
339
+ var ROUTE_COUNT_HIGH = 40;
340
+ function getWarningsForOpenAPI(openApi) {
341
+ if (openApi === null) {
342
+ return [
343
+ {
344
+ code: AUDIT_CODES.OPENAPI_NOT_FOUND,
345
+ severity: "info",
346
+ message: "No OpenAPI specification found at this origin.",
347
+ hint: "Expose an OpenAPI spec (e.g. /openapi.json) for richer discovery."
348
+ }
349
+ ];
350
+ }
351
+ const warnings = [];
352
+ if (openApi.routes.length === 0) {
353
+ warnings.push({
354
+ code: AUDIT_CODES.OPENAPI_NO_ROUTES,
355
+ severity: "warn",
356
+ message: "OpenAPI spec found but contains no route definitions.",
357
+ hint: "Add paths to your OpenAPI spec so agents can discover endpoints.",
358
+ path: "paths"
359
+ });
360
+ }
361
+ return warnings;
1225
362
  }
1226
- function parseProtocols(paymentInfo) {
1227
- const protocols = paymentInfo.protocols;
1228
- if (!Array.isArray(protocols)) return [];
1229
- return protocols.filter(
1230
- (entry) => typeof entry === "string" && entry.length > 0
1231
- );
363
+ function getWarningsForWellKnown(wellKnown) {
364
+ if (wellKnown === null) {
365
+ return [
366
+ {
367
+ code: AUDIT_CODES.WELLKNOWN_NOT_FOUND,
368
+ severity: "info",
369
+ message: "No /.well-known/x402 resource found at this origin.",
370
+ hint: "Expose /.well-known/x402 as a fallback for agents that cannot read OpenAPI."
371
+ }
372
+ ];
373
+ }
374
+ return [];
1232
375
  }
1233
- async function runOpenApiStage(options) {
376
+ function getWarningsForL2(l2) {
1234
377
  const warnings = [];
1235
- const resources = [];
1236
- let fetchedUrl;
1237
- let document;
1238
- for (const path of OPENAPI_PATH_CANDIDATES) {
1239
- const url = `${options.origin}${path}`;
1240
- try {
1241
- const response = await options.fetcher(url, {
1242
- method: "GET",
1243
- headers: { Accept: "application/json", ...options.headers },
1244
- signal: options.signal
378
+ if (l2.routes.length === 0) {
379
+ warnings.push({
380
+ code: AUDIT_CODES.L2_NO_ROUTES,
381
+ severity: "warn",
382
+ message: "No routes found from any discovery source."
383
+ });
384
+ return warnings;
385
+ }
386
+ if (l2.routes.length > ROUTE_COUNT_HIGH) {
387
+ warnings.push({
388
+ code: AUDIT_CODES.L2_ROUTE_COUNT_HIGH,
389
+ severity: "warn",
390
+ message: `High route count (${l2.routes.length}) may exceed agent token budgets for zero-hop injection.`,
391
+ hint: "Consider grouping routes or reducing the advertised surface."
392
+ });
393
+ }
394
+ for (const route of l2.routes) {
395
+ const loc = `${route.method} ${route.path}`;
396
+ if (!route.authMode) {
397
+ warnings.push({
398
+ code: AUDIT_CODES.L2_AUTH_MODE_MISSING,
399
+ severity: "warn",
400
+ message: `Route ${loc} is missing an auth mode declaration.`,
401
+ hint: "Set x-payment-info.authMode (or securitySchemes) so agents know if payment is required.",
402
+ path: route.path
1245
403
  });
1246
- if (!response.ok) {
1247
- if (response.status !== 404) {
1248
- warnings.push(
1249
- warning("FETCH_FAILED", "warn", `OpenAPI fetch failed (${response.status}) at ${url}`, {
1250
- stage: "openapi"
1251
- })
1252
- );
1253
- }
1254
- continue;
404
+ }
405
+ if (route.authMode === "paid") {
406
+ if (!route.price) {
407
+ warnings.push({
408
+ code: AUDIT_CODES.L2_PRICE_MISSING_ON_PAID,
409
+ severity: "warn",
410
+ message: `Paid route ${loc} has no price hint.`,
411
+ hint: "Add x-payment-info.price (or minPrice/maxPrice) so agents can budget before calling.",
412
+ path: route.path
413
+ });
1255
414
  }
1256
- const payload = await response.json();
1257
- if (!isRecord3(payload)) {
1258
- warnings.push(
1259
- warning("PARSE_FAILED", "error", `OpenAPI payload at ${url} is not a JSON object`, {
1260
- stage: "openapi"
1261
- })
1262
- );
1263
- continue;
415
+ if (!route.protocols?.length) {
416
+ warnings.push({
417
+ code: AUDIT_CODES.L2_PROTOCOLS_MISSING_ON_PAID,
418
+ severity: "info",
419
+ message: `Paid route ${loc} does not declare supported payment protocols.`,
420
+ hint: "Add x-payment-info.protocols (e.g. ['x402']) to signal which payment flows are accepted.",
421
+ path: route.path
422
+ });
1264
423
  }
1265
- document = payload;
1266
- fetchedUrl = url;
1267
- break;
1268
- } catch (error) {
1269
- warnings.push(
1270
- warning(
1271
- "FETCH_FAILED",
1272
- "warn",
1273
- `OpenAPI fetch exception at ${url}: ${error instanceof Error ? error.message : String(error)}`,
1274
- { stage: "openapi" }
1275
- )
1276
- );
1277
424
  }
1278
425
  }
1279
- if (!document || !fetchedUrl) {
1280
- return {
1281
- stage: "openapi",
1282
- valid: false,
1283
- resources: [],
1284
- warnings
1285
- };
426
+ return warnings;
427
+ }
428
+ function getWarningsForL3(l3) {
429
+ if (l3 === null) {
430
+ return [
431
+ {
432
+ code: AUDIT_CODES.L3_NOT_FOUND,
433
+ severity: "info",
434
+ message: "No spec data found for this endpoint.",
435
+ hint: "Ensure the path is defined in your OpenAPI spec or reachable via probe."
436
+ }
437
+ ];
1286
438
  }
1287
- const hasTopLevel = typeof document.openapi === "string" && isRecord3(document.info) && typeof document.info.title === "string" && typeof document.info.version === "string" && isRecord3(document.paths);
1288
- if (!hasTopLevel) {
1289
- warnings.push(
1290
- warning("OPENAPI_TOP_LEVEL_INVALID", "error", "OpenAPI required fields are missing", {
1291
- stage: "openapi"
1292
- })
1293
- );
1294
- return {
1295
- stage: "openapi",
1296
- valid: false,
1297
- resources: [],
1298
- warnings,
1299
- links: { openapiUrl: fetchedUrl },
1300
- ...options.includeRaw ? { raw: document } : {}
1301
- };
439
+ const warnings = [];
440
+ if (!l3.authMode) {
441
+ warnings.push({
442
+ code: AUDIT_CODES.L3_AUTH_MODE_MISSING,
443
+ severity: "warn",
444
+ message: "Endpoint has no auth mode in the spec.",
445
+ hint: "Declare auth mode via security schemes or x-payment-info so agents can plan payment."
446
+ });
1302
447
  }
1303
- const paths = document.paths;
1304
- const discovery = isRecord3(document["x-discovery"]) ? document["x-discovery"] : void 0;
1305
- const ownershipProofs = Array.isArray(discovery?.ownershipProofs) ? discovery.ownershipProofs.filter((entry) => typeof entry === "string") : [];
1306
- const llmsTxtUrl = asString(discovery?.llmsTxtUrl);
1307
- if (ownershipProofs.length > 0) {
1308
- warnings.push(
1309
- warning("OPENAPI_OWNERSHIP_PROOFS_PRESENT", "info", "OpenAPI ownership proofs detected", {
1310
- stage: "openapi"
1311
- })
1312
- );
448
+ if (l3.authMode === "paid" && !l3.inputSchema) {
449
+ warnings.push({
450
+ code: AUDIT_CODES.L3_INPUT_SCHEMA_MISSING,
451
+ severity: "warn",
452
+ message: "Paid endpoint is missing an input schema.",
453
+ hint: "Add a requestBody or parameters schema so agents can construct valid payloads."
454
+ });
1313
455
  }
1314
- for (const [rawPath, rawPathItem] of Object.entries(paths)) {
1315
- if (!isRecord3(rawPathItem)) {
1316
- warnings.push(
1317
- warning("OPENAPI_OPERATION_INVALID", "warn", `Path item ${rawPath} is not an object`, {
1318
- stage: "openapi"
1319
- })
1320
- );
1321
- continue;
1322
- }
1323
- for (const [rawMethod, rawOperation] of Object.entries(rawPathItem)) {
1324
- const method = parseMethod(rawMethod);
1325
- if (!method) continue;
1326
- if (!isRecord3(rawOperation)) {
1327
- warnings.push(
1328
- warning(
1329
- "OPENAPI_OPERATION_INVALID",
1330
- "warn",
1331
- `${rawMethod.toUpperCase()} ${rawPath} is not an object`,
1332
- {
1333
- stage: "openapi"
1334
- }
1335
- )
1336
- );
1337
- continue;
1338
- }
1339
- const operation = rawOperation;
1340
- const summary = asString(operation.summary) ?? asString(operation.description);
1341
- if (!summary) {
1342
- warnings.push(
1343
- warning(
1344
- "OPENAPI_SUMMARY_MISSING",
1345
- "warn",
1346
- `${method} ${rawPath} missing summary/description, using fallback summary`,
1347
- { stage: "openapi" }
1348
- )
1349
- );
1350
- }
1351
- const authMode = inferAuthMode(operation);
1352
- if (!authMode) {
1353
- warnings.push(
1354
- warning(
1355
- "OPENAPI_AUTH_MODE_MISSING",
1356
- "error",
1357
- `${method} ${rawPath} missing auth mode (no x-payment-info, 402 response, or security scheme)`,
1358
- {
1359
- stage: "openapi"
1360
- }
1361
- )
1362
- );
1363
- continue;
1364
- }
1365
- if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) {
1366
- warnings.push(
1367
- warning(
1368
- "OPENAPI_402_MISSING",
1369
- "error",
1370
- `${method} ${rawPath} requires 402 response for authMode=${authMode}`,
1371
- { stage: "openapi" }
1372
- )
1373
- );
1374
- continue;
1375
- }
1376
- const paymentInfo = isRecord3(operation["x-payment-info"]) ? operation["x-payment-info"] : void 0;
1377
- const protocols = paymentInfo ? parseProtocols(paymentInfo) : [];
1378
- if (authMode === "paid" && protocols.length === 0) {
1379
- warnings.push(
1380
- warning(
1381
- "OPENAPI_PAID_PROTOCOLS_MISSING",
1382
- "error",
1383
- `${method} ${rawPath} must define x-payment-info.protocols when authMode=paid`,
1384
- { stage: "openapi" }
1385
- )
1386
- );
1387
- continue;
1388
- }
1389
- let pricing;
1390
- let priceHint;
1391
- if (paymentInfo && authMode === "paid") {
1392
- const pricingModeRaw = asString(paymentInfo.pricingMode);
1393
- const price = parsePriceValue(paymentInfo.price);
1394
- const minPrice = parsePriceValue(paymentInfo.minPrice);
1395
- const maxPrice = parsePriceValue(paymentInfo.maxPrice);
1396
- const inferredPricingMode = pricingModeRaw ?? (price ? "fixed" : minPrice && maxPrice ? "range" : "quote");
1397
- if (inferredPricingMode !== "fixed" && inferredPricingMode !== "range" && inferredPricingMode !== "quote") {
1398
- warnings.push(
1399
- warning(
1400
- "OPENAPI_PRICING_INVALID",
1401
- "error",
1402
- `${method} ${rawPath} has invalid pricingMode`,
1403
- {
1404
- stage: "openapi"
1405
- }
1406
- )
1407
- );
1408
- continue;
1409
- }
1410
- if (inferredPricingMode === "fixed" && !price) {
1411
- warnings.push(
1412
- warning(
1413
- "OPENAPI_PRICING_INVALID",
1414
- "error",
1415
- `${method} ${rawPath} fixed pricing requires price`,
1416
- {
1417
- stage: "openapi"
1418
- }
1419
- )
1420
- );
1421
- continue;
1422
- }
1423
- if (inferredPricingMode === "range") {
1424
- if (!minPrice || !maxPrice) {
1425
- warnings.push(
1426
- warning(
1427
- "OPENAPI_PRICING_INVALID",
1428
- "error",
1429
- `${method} ${rawPath} range pricing requires minPrice and maxPrice`,
1430
- { stage: "openapi" }
1431
- )
1432
- );
1433
- continue;
1434
- }
1435
- const min = Number(minPrice);
1436
- const max = Number(maxPrice);
1437
- if (!Number.isFinite(min) || !Number.isFinite(max) || min > max) {
1438
- warnings.push(
1439
- warning(
1440
- "OPENAPI_PRICING_INVALID",
1441
- "error",
1442
- `${method} ${rawPath} range pricing requires numeric minPrice <= maxPrice`,
1443
- { stage: "openapi" }
1444
- )
1445
- );
1446
- continue;
1447
- }
1448
- }
1449
- pricing = {
1450
- pricingMode: inferredPricingMode,
1451
- ...price ? { price } : {},
1452
- ...minPrice ? { minPrice } : {},
1453
- ...maxPrice ? { maxPrice } : {}
1454
- };
1455
- priceHint = inferredPricingMode === "fixed" ? price : inferredPricingMode === "range" ? `${minPrice}-${maxPrice}` : maxPrice;
456
+ if (l3.authMode === "paid" && !l3.protocols?.length) {
457
+ warnings.push({
458
+ code: AUDIT_CODES.L3_PROTOCOLS_MISSING_ON_PAID,
459
+ severity: "info",
460
+ message: "Paid endpoint does not declare supported payment protocols.",
461
+ hint: "Add x-payment-info.protocols (e.g. ['x402']) to the operation."
462
+ });
463
+ }
464
+ return warnings;
465
+ }
466
+ function getWarningsForL4(l4) {
467
+ if (l4 === null) {
468
+ return [
469
+ {
470
+ code: AUDIT_CODES.L4_GUIDANCE_MISSING,
471
+ severity: "info",
472
+ message: "No guidance text found (llms.txt or OpenAPI info.guidance).",
473
+ hint: "Add an info.guidance field to your OpenAPI spec or expose /llms.txt for agent-readable instructions."
1456
474
  }
1457
- const resource = createResource(
1458
- {
1459
- origin: options.origin,
1460
- method,
1461
- path: normalizePath(rawPath),
1462
- source: "openapi",
1463
- summary: summary ?? `${method} ${normalizePath(rawPath)}`,
1464
- authHint: authMode,
1465
- protocolHints: protocols,
1466
- ...priceHint ? { priceHint } : {},
1467
- ...pricing ? { pricing } : {},
1468
- links: {
1469
- openapiUrl: fetchedUrl,
1470
- ...llmsTxtUrl ? { llmsTxtUrl } : {}
1471
- }
1472
- },
1473
- 0.95,
1474
- ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
1475
- );
1476
- resources.push(resource);
1477
- }
475
+ ];
1478
476
  }
1479
- if (llmsTxtUrl) {
1480
- try {
1481
- const llmsResponse = await options.fetcher(llmsTxtUrl, {
1482
- method: "GET",
1483
- headers: { Accept: "text/plain", ...options.headers },
1484
- signal: options.signal
1485
- });
1486
- if (llmsResponse.ok) {
1487
- const llmsText = await llmsResponse.text();
1488
- const tokenCount = estimateTokenCount(llmsText);
1489
- if (tokenCount > LLMS_TOKEN_WARNING_THRESHOLD) {
1490
- warnings.push(
1491
- warning(
1492
- "LLMSTXT_TOO_LARGE",
1493
- "warn",
1494
- `llms.txt estimated ${tokenCount} tokens (threshold ${LLMS_TOKEN_WARNING_THRESHOLD})`,
1495
- {
1496
- stage: "openapi",
1497
- hint: "Keep llms.txt concise and domain-level only."
1498
- }
1499
- )
1500
- );
1501
- }
1502
- if (options.includeRaw) {
1503
- return {
1504
- stage: "openapi",
1505
- valid: resources.length > 0,
1506
- resources,
1507
- warnings,
1508
- links: { openapiUrl: fetchedUrl, llmsTxtUrl },
1509
- raw: {
1510
- openapi: document,
1511
- llmsTxt: llmsText
1512
- }
1513
- };
1514
- }
1515
- } else {
1516
- warnings.push(
1517
- warning(
1518
- "LLMSTXT_FETCH_FAILED",
1519
- "warn",
1520
- `llms.txt fetch failed (${llmsResponse.status})`,
1521
- {
1522
- stage: "openapi"
1523
- }
1524
- )
1525
- );
477
+ if (l4.guidance.length > GUIDANCE_TOO_LONG_CHARS) {
478
+ return [
479
+ {
480
+ code: AUDIT_CODES.L4_GUIDANCE_TOO_LONG,
481
+ severity: "warn",
482
+ message: `Guidance text is ${l4.guidance.length} characters, which may exceed zero-hop token budgets.`,
483
+ hint: "Trim guidance to under ~4000 characters for reliable zero-hop context injection."
1526
484
  }
1527
- } catch (error) {
1528
- warnings.push(
1529
- warning(
1530
- "LLMSTXT_FETCH_FAILED",
1531
- "warn",
1532
- `llms.txt fetch failed: ${error instanceof Error ? error.message : String(error)}`,
1533
- { stage: "openapi" }
1534
- )
1535
- );
1536
- }
485
+ ];
1537
486
  }
487
+ return [];
488
+ }
489
+
490
+ // src/core/source/probe/index.ts
491
+ import { ResultAsync as ResultAsync4 } from "neverthrow";
492
+
493
+ // src/core/protocols/x402/v1/schema.ts
494
+ function extractSchemas(accepts) {
495
+ const first = accepts[0];
496
+ if (!isRecord(first)) return {};
497
+ const outputSchema = isRecord(first.outputSchema) ? first.outputSchema : void 0;
1538
498
  return {
1539
- stage: "openapi",
1540
- valid: resources.length > 0,
1541
- resources,
1542
- warnings,
1543
- links: {
1544
- openapiUrl: fetchedUrl,
1545
- ...llmsTxtUrl ? { llmsTxtUrl } : {}
1546
- },
1547
- ...options.includeRaw ? { raw: { openapi: document } } : {}
499
+ ...outputSchema && isRecord(outputSchema.input) ? { inputSchema: outputSchema.input } : {},
500
+ ...outputSchema && isRecord(outputSchema.output) ? { outputSchema: outputSchema.output } : {}
1548
501
  };
1549
502
  }
1550
503
 
1551
- // src/core/probe.ts
1552
- function detectAuthHintFrom402Payload(payload) {
1553
- if (payload && typeof payload === "object") {
1554
- const asRecord = payload;
1555
- const extensions = asRecord.extensions;
1556
- if (extensions && typeof extensions === "object") {
1557
- const extRecord = extensions;
1558
- if (extRecord["sign-in-with-x"]) return "siwx";
1559
- }
504
+ // src/core/protocols/x402/shared.ts
505
+ function asNonEmptyString(value) {
506
+ if (typeof value !== "string") return void 0;
507
+ const trimmed = value.trim();
508
+ return trimmed.length > 0 ? trimmed : void 0;
509
+ }
510
+ function asPositiveNumber(value) {
511
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
512
+ }
513
+
514
+ // src/core/protocols/x402/v1/payment-options.ts
515
+ function extractPaymentOptions(accepts) {
516
+ return accepts.flatMap((accept) => {
517
+ if (!isRecord(accept)) return [];
518
+ const network = asNonEmptyString(accept.network);
519
+ const asset = asNonEmptyString(accept.asset);
520
+ const maxAmountRequired = asNonEmptyString(accept.maxAmountRequired);
521
+ if (!network || !asset || !maxAmountRequired) return [];
522
+ const scheme = asNonEmptyString(accept.scheme);
523
+ const payTo = asNonEmptyString(accept.payTo);
524
+ const maxTimeoutSeconds = asPositiveNumber(accept.maxTimeoutSeconds);
525
+ return [
526
+ {
527
+ protocol: "x402",
528
+ version: 1,
529
+ network,
530
+ asset,
531
+ maxAmountRequired,
532
+ ...scheme ? { scheme } : {},
533
+ ...payTo ? { payTo } : {},
534
+ ...maxTimeoutSeconds ? { maxTimeoutSeconds } : {}
535
+ }
536
+ ];
537
+ });
538
+ }
539
+
540
+ // src/core/protocols/x402/v2/schema.ts
541
+ function extractSchemas2(payload) {
542
+ if (!isRecord(payload)) return {};
543
+ const extensions = isRecord(payload.extensions) ? payload.extensions : void 0;
544
+ const bazaar = extensions && isRecord(extensions.bazaar) ? extensions.bazaar : void 0;
545
+ const schema = bazaar && isRecord(bazaar.schema) ? bazaar.schema : void 0;
546
+ if (!schema) return {};
547
+ const schemaProps = isRecord(schema.properties) ? schema.properties : void 0;
548
+ if (!schemaProps) return {};
549
+ const inputProps = isRecord(schemaProps.input) ? schemaProps.input : void 0;
550
+ const inputProperties = inputProps && isRecord(inputProps.properties) ? inputProps.properties : void 0;
551
+ const inputSchema = (inputProperties && isRecord(inputProperties.body) ? inputProperties.body : void 0) ?? (inputProperties && isRecord(inputProperties.queryParams) ? inputProperties.queryParams : void 0);
552
+ const outputProps = isRecord(schemaProps.output) ? schemaProps.output : void 0;
553
+ const outputProperties = outputProps && isRecord(outputProps.properties) ? outputProps.properties : void 0;
554
+ const outputSchema = outputProperties && isRecord(outputProperties.example) ? outputProperties.example : void 0;
555
+ return {
556
+ ...inputSchema ? { inputSchema } : {},
557
+ ...outputSchema ? { outputSchema } : {}
558
+ };
559
+ }
560
+
561
+ // src/core/protocols/x402/v2/payment-options.ts
562
+ function extractPaymentOptions2(accepts) {
563
+ return accepts.flatMap((accept) => {
564
+ if (!isRecord(accept)) return [];
565
+ const network = asNonEmptyString(accept.network);
566
+ const asset = asNonEmptyString(accept.asset);
567
+ const amount = asNonEmptyString(accept.amount);
568
+ if (!network || !asset || !amount) return [];
569
+ const scheme = asNonEmptyString(accept.scheme);
570
+ const payTo = asNonEmptyString(accept.payTo);
571
+ const maxTimeoutSeconds = asPositiveNumber(accept.maxTimeoutSeconds);
572
+ return [
573
+ {
574
+ protocol: "x402",
575
+ version: 2,
576
+ network,
577
+ asset,
578
+ amount,
579
+ ...scheme ? { scheme } : {},
580
+ ...payTo ? { payTo } : {},
581
+ ...maxTimeoutSeconds ? { maxTimeoutSeconds } : {}
582
+ }
583
+ ];
584
+ });
585
+ }
586
+
587
+ // src/core/protocols/x402/index.ts
588
+ function parseVersion(payload) {
589
+ if (!isRecord(payload)) return void 0;
590
+ const v = payload.x402Version;
591
+ if (v === 1 || v === 2) return v;
592
+ return void 0;
593
+ }
594
+ function detectAuthHint(payload) {
595
+ if (isRecord(payload) && isRecord(payload.extensions)) {
596
+ if (payload.extensions["api-key"]) return "apiKey+paid";
597
+ if (payload.extensions["sign-in-with-x"]) return "siwx";
1560
598
  }
1561
599
  return "paid";
1562
600
  }
601
+ function extractPaymentOptions3(payload) {
602
+ if (!isRecord(payload)) return [];
603
+ const version = parseVersion(payload);
604
+ const accepts = Array.isArray(payload.accepts) ? payload.accepts : [];
605
+ if (version === 1) return extractPaymentOptions(accepts);
606
+ if (version === 2) return extractPaymentOptions2(accepts);
607
+ return [];
608
+ }
609
+ function extractSchemas3(payload) {
610
+ if (!isRecord(payload)) return {};
611
+ const version = parseVersion(payload);
612
+ if (version === 2) return extractSchemas2(payload);
613
+ if (version === 1) {
614
+ const accepts = Array.isArray(payload.accepts) ? payload.accepts : [];
615
+ return extractSchemas(accepts);
616
+ }
617
+ return {};
618
+ }
619
+ function parseInputSchema(payload) {
620
+ return extractSchemas3(payload).inputSchema;
621
+ }
622
+ function parseOutputSchema(payload) {
623
+ return extractSchemas3(payload).outputSchema;
624
+ }
625
+
626
+ // src/core/protocols/x402/v1/parse-payment-required.ts
627
+ async function parsePaymentRequiredBody(response) {
628
+ const payload = await response.clone().json();
629
+ if (!isRecord(payload) || payload.x402Version !== 1) return null;
630
+ return payload;
631
+ }
632
+
633
+ // src/core/protocols/x402/v2/parse-payment-required.ts
634
+ function parsePaymentRequiredBody2(headerValue) {
635
+ const payload = JSON.parse(atob(headerValue));
636
+ if (!isRecord(payload) || payload.x402Version !== 2) return null;
637
+ return payload;
638
+ }
639
+
640
+ // src/core/source/probe/index.ts
641
+ function isUsableStatus(status) {
642
+ return status === 402 || status >= 200 && status < 300;
643
+ }
1563
644
  function detectProtocols(response) {
1564
645
  const protocols = /* @__PURE__ */ new Set();
1565
646
  const directHeader = response.headers.get("x-payment-protocol");
@@ -1574,767 +655,548 @@ function detectProtocols(response) {
1574
655
  if (isMmmEnabled() && authHeader.includes("mpp")) protocols.add("mpp");
1575
656
  return [...protocols];
1576
657
  }
1577
- async function runProbeStage(options) {
1578
- const candidates = options.probeCandidates ?? [];
1579
- if (candidates.length === 0) {
1580
- return {
1581
- stage: "probe",
1582
- valid: false,
1583
- resources: [],
1584
- warnings: [
1585
- warning(
1586
- "PROBE_STAGE_SKIPPED",
1587
- "info",
1588
- "Probe stage skipped because no probe candidates were provided.",
1589
- { stage: "probe" }
1590
- )
1591
- ]
1592
- };
658
+ function buildProbeUrl(url, method, inputBody) {
659
+ if (!inputBody || method !== "GET" && method !== "HEAD") return url;
660
+ const u = new URL(url);
661
+ for (const [key, value] of Object.entries(inputBody)) {
662
+ u.searchParams.set(key, String(value));
1593
663
  }
1594
- const resources = [];
1595
- const warnings = [];
1596
- for (const candidate of candidates) {
1597
- const path = normalizePath(candidate.path);
1598
- const methods = candidate.methods?.length ? candidate.methods : DEFAULT_PROBE_METHODS;
1599
- for (const method of methods) {
1600
- const url = `${options.origin}${path}`;
1601
- try {
1602
- const response = await options.fetcher(url, {
1603
- method,
1604
- headers: {
1605
- Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
1606
- ...options.headers
1607
- },
1608
- signal: options.signal
1609
- });
1610
- if (response.status !== 402 && (response.status < 200 || response.status >= 300)) {
1611
- continue;
1612
- }
664
+ return u.toString();
665
+ }
666
+ function probeMethod(url, method, path, headers, signal, inputBody) {
667
+ const hasBody = inputBody !== void 0 && method !== "GET" && method !== "HEAD";
668
+ const probeUrl = buildProbeUrl(url, method, inputBody);
669
+ return fetchSafe(probeUrl, {
670
+ method,
671
+ headers: {
672
+ Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
673
+ ...hasBody ? { "Content-Type": "application/json" } : {},
674
+ ...headers
675
+ },
676
+ ...hasBody ? { body: JSON.stringify(inputBody) } : {},
677
+ signal
678
+ }).andThen((response) => {
679
+ if (!isUsableStatus(response.status)) return ResultAsync4.fromSafePromise(Promise.resolve(null));
680
+ return ResultAsync4.fromSafePromise(
681
+ (async () => {
1613
682
  let authHint = response.status === 402 ? "paid" : "unprotected";
683
+ let paymentRequiredBody;
1614
684
  if (response.status === 402) {
1615
685
  try {
1616
- const payload = await response.clone().json();
1617
- authHint = detectAuthHintFrom402Payload(payload);
686
+ const headerValue = response.headers.get("payment-required");
687
+ const parsed = headerValue !== null ? parsePaymentRequiredBody2(headerValue) : await parsePaymentRequiredBody(response);
688
+ if (parsed !== null) {
689
+ paymentRequiredBody = parsed;
690
+ authHint = detectAuthHint(paymentRequiredBody);
691
+ }
1618
692
  } catch {
1619
693
  }
1620
694
  }
1621
- const protocolHints = detectProtocols(response);
1622
- resources.push(
1623
- createResource(
1624
- {
1625
- origin: options.origin,
1626
- method,
1627
- path,
1628
- source: "probe",
1629
- summary: `${method} ${path}`,
1630
- authHint,
1631
- ...protocolHints.length ? { protocolHints } : {}
1632
- },
1633
- 0.6,
1634
- "runtime_verified"
1635
- )
1636
- );
1637
- } catch (error) {
1638
- warnings.push(
1639
- warning(
1640
- "FETCH_FAILED",
1641
- "info",
1642
- `Probe ${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`,
1643
- { stage: "probe" }
1644
- )
1645
- );
1646
- }
1647
- }
1648
- }
1649
- return {
1650
- stage: "probe",
1651
- valid: resources.length > 0,
1652
- resources,
1653
- warnings
1654
- };
695
+ const protocols = detectProtocols(response);
696
+ const wwwAuthenticate = response.headers.get("www-authenticate") ?? void 0;
697
+ return {
698
+ path,
699
+ method,
700
+ authHint,
701
+ ...protocols.length ? { protocols } : {},
702
+ ...paymentRequiredBody !== void 0 ? { paymentRequiredBody } : {},
703
+ ...wwwAuthenticate ? { wwwAuthenticate } : {}
704
+ };
705
+ })()
706
+ );
707
+ });
1655
708
  }
1656
-
1657
- // src/core/upgrade.ts
1658
- var UPGRADE_WARNING_CODES = [
1659
- "LEGACY_WELL_KNOWN_USED",
1660
- "LEGACY_DNS_USED",
1661
- "LEGACY_DNS_PLAIN_URL",
1662
- "LEGACY_INSTRUCTIONS_USED",
1663
- "LEGACY_OWNERSHIP_PROOFS_USED",
1664
- "OPENAPI_AUTH_MODE_MISSING",
1665
- "OPENAPI_TOP_LEVEL_INVALID"
1666
- ];
1667
- var UPGRADE_WARNING_CODE_SET = new Set(UPGRADE_WARNING_CODES);
1668
- function computeUpgradeSignal(warnings) {
1669
- const reasons = warnings.map((entry) => entry.code).filter((code, index, list) => list.indexOf(code) === index).filter((code) => UPGRADE_WARNING_CODE_SET.has(code));
1670
- return {
1671
- upgradeSuggested: reasons.length > 0,
1672
- upgradeReasons: reasons
1673
- };
709
+ function getProbe(url, headers, signal, inputBody) {
710
+ const path = normalizePath(new URL(url).pathname || "/");
711
+ return ResultAsync4.fromSafePromise(
712
+ Promise.all(
713
+ [...HTTP_METHODS].map(
714
+ (method) => probeMethod(url, method, path, headers, signal, inputBody).match(
715
+ (result) => result,
716
+ () => null
717
+ )
718
+ )
719
+ ).then((results) => results.filter((r) => r !== null))
720
+ );
1674
721
  }
1675
722
 
1676
- // src/core/discovery.ts
1677
- function mergeResources(target, incoming, resourceWarnings) {
1678
- const warnings = [];
1679
- for (const resource of incoming) {
1680
- const existing = target.get(resource.resourceKey);
1681
- if (!existing) {
1682
- target.set(resource.resourceKey, resource);
1683
- continue;
1684
- }
1685
- const conflict = existing.authHint !== resource.authHint || existing.priceHint !== resource.priceHint || JSON.stringify(existing.protocolHints ?? []) !== JSON.stringify(resource.protocolHints ?? []);
1686
- if (conflict) {
1687
- const conflictWarning = warning(
1688
- "CROSS_SOURCE_CONFLICT",
1689
- "warn",
1690
- `Resource conflict for ${resource.resourceKey}; keeping higher-precedence source ${existing.source} over ${resource.source}.`,
1691
- {
1692
- resourceKey: resource.resourceKey
1693
- }
1694
- );
1695
- warnings.push(conflictWarning);
1696
- resourceWarnings[resource.resourceKey] = [
1697
- ...resourceWarnings[resource.resourceKey] ?? [],
1698
- conflictWarning
1699
- ];
1700
- continue;
1701
- }
1702
- target.set(resource.resourceKey, {
1703
- ...existing,
1704
- summary: existing.summary ?? resource.summary,
1705
- links: { ...resource.links, ...existing.links },
1706
- confidence: Math.max(existing.confidence ?? 0, resource.confidence ?? 0)
1707
- });
1708
- }
1709
- return warnings;
1710
- }
1711
- function toTrace(stageResult, startedAt) {
1712
- return {
1713
- stage: stageResult.stage,
1714
- attempted: true,
1715
- valid: stageResult.valid,
1716
- resourceCount: stageResult.resources.length,
1717
- durationMs: Date.now() - startedAt,
1718
- warnings: stageResult.warnings,
1719
- ...stageResult.links ? { links: stageResult.links } : {}
1720
- };
1721
- }
1722
- async function runOverrideStage(options) {
1723
- const warnings = [];
1724
- for (const overrideUrl of options.overrideUrls) {
1725
- const normalized = overrideUrl.trim();
1726
- if (!normalized) continue;
723
+ // src/core/protocols/mpp/index.ts
724
+ var TEMPO_DEFAULT_CHAIN_ID = 4217;
725
+ function parseAuthParams(segment) {
726
+ const params = {};
727
+ const re = /(\w+)=(?:"([^"]*)"|'([^']*)')/g;
728
+ let match;
729
+ while ((match = re.exec(segment)) !== null) {
730
+ params[match[1]] = match[2] ?? match[3] ?? "";
731
+ }
732
+ return params;
733
+ }
734
+ function extractPaymentOptions4(wwwAuthenticate) {
735
+ if (!isMmmEnabled() || !wwwAuthenticate) return [];
736
+ const options = [];
737
+ for (const segment of wwwAuthenticate.split(/,\s*(?=Payment\s)/i)) {
738
+ const stripped = segment.replace(/^Payment\s+/i, "").trim();
739
+ const params = parseAuthParams(stripped);
740
+ const paymentMethod = params["method"];
741
+ const intent = params["intent"];
742
+ const realm = params["realm"];
743
+ const description = params["description"];
744
+ const requestStr = params["request"];
745
+ if (!paymentMethod || !intent || !realm || !requestStr) continue;
746
+ let request;
1727
747
  try {
1728
- const response = await options.fetcher(normalized, {
1729
- method: "GET",
1730
- headers: { Accept: "application/json, text/plain;q=0.9", ...options.headers },
1731
- signal: options.signal
1732
- });
1733
- if (!response.ok) {
1734
- warnings.push(
1735
- warning(
1736
- "FETCH_FAILED",
1737
- "warn",
1738
- `Override fetch failed (${response.status}) at ${normalized}`,
1739
- {
1740
- stage: "override"
1741
- }
1742
- )
1743
- );
1744
- continue;
1745
- }
1746
- const bodyText = await response.text();
1747
- let parsedJson;
1748
- try {
1749
- parsedJson = JSON.parse(bodyText);
1750
- } catch {
1751
- parsedJson = void 0;
1752
- }
1753
- if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson) && "openapi" in parsedJson && "paths" in parsedJson) {
1754
- const openapiResult = await runOpenApiStage({
1755
- origin: options.origin,
1756
- fetcher: async (input, init) => {
1757
- const inputString = String(input);
1758
- if (inputString === `${options.origin}/openapi.json` || inputString === `${options.origin}/.well-known/openapi.json`) {
1759
- return new Response(bodyText, {
1760
- status: 200,
1761
- headers: { "content-type": "application/json" }
1762
- });
1763
- }
1764
- return options.fetcher(input, init);
1765
- },
1766
- headers: options.headers,
1767
- signal: options.signal,
1768
- includeRaw: options.includeRaw
1769
- });
1770
- return {
1771
- ...openapiResult,
1772
- stage: "override",
1773
- warnings: [
1774
- warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1775
- stage: "override"
1776
- }),
1777
- ...openapiResult.warnings
1778
- ]
1779
- };
1780
- }
1781
- if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson)) {
1782
- const parsedWellKnown = parseWellKnownPayload(parsedJson, options.origin, normalized);
1783
- if (parsedWellKnown.resources.length > 0) {
1784
- return {
1785
- stage: "override",
1786
- valid: true,
1787
- resources: parsedWellKnown.resources,
1788
- warnings: [
1789
- warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1790
- stage: "override"
1791
- }),
1792
- ...parsedWellKnown.warnings
1793
- ],
1794
- links: { discoveryUrl: normalized },
1795
- ...options.includeRaw ? { raw: parsedWellKnown.raw } : {}
1796
- };
1797
- }
1798
- }
1799
- } catch (error) {
1800
- warnings.push(
1801
- warning(
1802
- "FETCH_FAILED",
1803
- "warn",
1804
- `Override fetch exception at ${normalized}: ${error instanceof Error ? error.message : String(error)}`,
1805
- { stage: "override" }
1806
- )
1807
- );
748
+ const parsed = JSON.parse(requestStr);
749
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
750
+ request = parsed;
751
+ } catch {
1808
752
  continue;
1809
753
  }
1810
- const fallbackWellKnownResult = await runWellKnownX402Stage({
1811
- origin: options.origin,
1812
- url: normalized,
1813
- fetcher: options.fetcher,
1814
- headers: options.headers,
1815
- signal: options.signal,
1816
- includeRaw: options.includeRaw
754
+ const asset = typeof request["currency"] === "string" ? request["currency"] : void 0;
755
+ const amountRaw = request["amount"];
756
+ const amount = typeof amountRaw === "string" ? amountRaw : typeof amountRaw === "number" ? String(amountRaw) : void 0;
757
+ const decimals = typeof request["decimals"] === "number" ? request["decimals"] : void 0;
758
+ const payTo = typeof request["recipient"] === "string" ? request["recipient"] : void 0;
759
+ const methodDetails = request["methodDetails"];
760
+ const chainId = methodDetails !== null && typeof methodDetails === "object" && !Array.isArray(methodDetails) && typeof methodDetails["chainId"] === "number" ? methodDetails["chainId"] : TEMPO_DEFAULT_CHAIN_ID;
761
+ if (!asset || !amount) continue;
762
+ options.push({
763
+ protocol: "mpp",
764
+ // isMmmEnabled
765
+ paymentMethod,
766
+ intent,
767
+ realm,
768
+ network: `tempo:${String(chainId)}`,
769
+ // isMmmEnabled
770
+ asset,
771
+ amount,
772
+ ...decimals != null ? { decimals } : {},
773
+ ...payTo ? { payTo } : {},
774
+ ...description ? { description } : {}
1817
775
  });
1818
- if (fallbackWellKnownResult.valid) {
1819
- return {
1820
- ...fallbackWellKnownResult,
1821
- stage: "override",
1822
- warnings: [
1823
- warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1824
- stage: "override"
1825
- }),
1826
- ...fallbackWellKnownResult.warnings
1827
- ]
1828
- };
1829
- }
1830
- warnings.push(...fallbackWellKnownResult.warnings);
1831
776
  }
1832
- return {
1833
- stage: "override",
1834
- valid: false,
1835
- resources: [],
1836
- warnings
1837
- };
777
+ return options;
1838
778
  }
1839
- async function runDiscovery({
1840
- detailed,
1841
- options
1842
- }) {
1843
- const fetcher = options.fetcher ?? fetch;
1844
- const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
1845
- const strictCompat = compatMode === "strict";
1846
- const rawView = detailed ? options.rawView ?? "none" : "none";
1847
- const includeRaw = rawView === "full";
1848
- const includeInteropMpp = detailed && Boolean(options.includeInteropMpp);
1849
- const origin = normalizeOrigin(options.target);
1850
- const warnings = [];
1851
- const trace = [];
1852
- const resourceWarnings = {};
1853
- const rawSources = {};
1854
- const merged = /* @__PURE__ */ new Map();
1855
- if (compatMode !== "off") {
1856
- warnings.push(
1857
- warning(
1858
- "COMPAT_MODE_ENABLED",
1859
- compatMode === "strict" ? "warn" : "info",
1860
- `Compatibility mode is '${compatMode}'. Legacy adapters are active.`
1861
- )
1862
- );
1863
- }
1864
- const stages = [];
1865
- if (options.overrideUrls && options.overrideUrls.length > 0) {
1866
- stages.push(
1867
- () => runOverrideStage({
1868
- origin,
1869
- overrideUrls: options.overrideUrls ?? [],
1870
- fetcher,
1871
- headers: options.headers,
1872
- signal: options.signal,
1873
- includeRaw
1874
- })
1875
- );
1876
- }
1877
- stages.push(
1878
- () => runOpenApiStage({
1879
- origin,
1880
- fetcher,
1881
- headers: options.headers,
1882
- signal: options.signal,
1883
- includeRaw
1884
- })
1885
- );
1886
- if (compatMode !== "off") {
1887
- stages.push(
1888
- () => runWellKnownX402Stage({
1889
- origin,
1890
- fetcher,
1891
- headers: options.headers,
1892
- signal: options.signal,
1893
- includeRaw
1894
- })
1895
- );
1896
- stages.push(
1897
- () => runDnsStage({
1898
- origin,
1899
- fetcher,
1900
- txtResolver: options.txtResolver,
1901
- headers: options.headers,
1902
- signal: options.signal,
1903
- includeRaw
1904
- })
1905
- );
1906
- if (includeInteropMpp && isMmmEnabled()) {
1907
- stages.push(
1908
- () => runInteropMppStage({
1909
- origin,
1910
- fetcher,
1911
- headers: options.headers,
1912
- signal: options.signal,
1913
- includeRaw
1914
- })
1915
- );
779
+
780
+ // src/core/layers/l3.ts
781
+ function findMatchingOpenApiPath(paths, targetPath) {
782
+ const exact = paths[targetPath];
783
+ if (isRecord(exact)) return { matchedPath: targetPath, pathItem: exact };
784
+ for (const [specPath, entry] of Object.entries(paths)) {
785
+ if (!isRecord(entry)) continue;
786
+ const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
787
+ const regex = new RegExp(`^${pattern}$`);
788
+ if (regex.test(targetPath)) {
789
+ return { matchedPath: specPath, pathItem: entry };
1916
790
  }
1917
791
  }
1918
- stages.push(
1919
- () => runProbeStage({
1920
- origin,
1921
- fetcher,
1922
- headers: options.headers,
1923
- signal: options.signal,
1924
- probeCandidates: options.probeCandidates
1925
- })
1926
- );
1927
- let selectedStage;
1928
- for (const runStage of stages) {
1929
- const startedAt = Date.now();
1930
- const stageResult = await runStage();
1931
- stageResult.warnings = applyStrictEscalation(stageResult.warnings, strictCompat);
1932
- trace.push(toTrace(stageResult, startedAt));
1933
- warnings.push(...stageResult.warnings);
1934
- if (stageResult.resources.length > 0) {
1935
- for (const stageWarning of stageResult.warnings) {
1936
- if (stageWarning.resourceKey) {
1937
- resourceWarnings[stageWarning.resourceKey] = [
1938
- ...resourceWarnings[stageWarning.resourceKey] ?? [],
1939
- stageWarning
1940
- ];
1941
- }
792
+ return null;
793
+ }
794
+ function resolveRef(document, ref, seen) {
795
+ if (!ref.startsWith("#/")) return void 0;
796
+ if (seen.has(ref)) return { $circular: ref };
797
+ seen.add(ref);
798
+ const parts = ref.slice(2).split("/");
799
+ let current = document;
800
+ for (const part of parts) {
801
+ if (!isRecord(current)) return void 0;
802
+ current = current[part];
803
+ if (current === void 0) return void 0;
804
+ }
805
+ if (isRecord(current)) return resolveRefs(document, current, seen);
806
+ return current;
807
+ }
808
+ function resolveRefs(document, obj, seen, depth = 0) {
809
+ if (depth > 4) return obj;
810
+ const resolved = {};
811
+ for (const [key, value] of Object.entries(obj)) {
812
+ if (key === "$ref" && typeof value === "string") {
813
+ const deref = resolveRef(document, value, seen);
814
+ if (isRecord(deref)) {
815
+ Object.assign(resolved, deref);
816
+ } else {
817
+ resolved[key] = value;
1942
818
  }
819
+ continue;
1943
820
  }
1944
- if (includeRaw && stageResult.raw !== void 0) {
1945
- if (stageResult.stage === "openapi" || stageResult.stage === "override") {
1946
- const rawObject = stageResult.raw;
1947
- if (rawObject.openapi !== void 0) rawSources.openapi = rawObject.openapi;
1948
- if (typeof rawObject.llmsTxt === "string") rawSources.llmsTxt = rawObject.llmsTxt;
1949
- }
1950
- if (stageResult.stage === "well-known/x402" || stageResult.stage === "override") {
1951
- rawSources.wellKnownX402 = [...rawSources.wellKnownX402 ?? [], stageResult.raw];
1952
- }
1953
- if (stageResult.stage === "dns/_x402") {
1954
- const rawObject = stageResult.raw;
1955
- if (Array.isArray(rawObject.dnsRecords)) {
1956
- rawSources.dnsRecords = rawObject.dnsRecords;
1957
- }
1958
- }
1959
- if (stageResult.stage === "interop/mpp") {
1960
- rawSources.interopMpp = stageResult.raw;
1961
- }
821
+ if (isRecord(value)) {
822
+ resolved[key] = resolveRefs(document, value, seen, depth + 1);
823
+ continue;
1962
824
  }
1963
- if (!stageResult.valid) {
825
+ if (Array.isArray(value)) {
826
+ resolved[key] = value.map(
827
+ (item) => isRecord(item) ? resolveRefs(document, item, seen, depth + 1) : item
828
+ );
1964
829
  continue;
1965
830
  }
1966
- const mergeWarnings = mergeResources(merged, stageResult.resources, resourceWarnings);
1967
- warnings.push(...mergeWarnings);
1968
- if (!detailed) {
1969
- selectedStage = selectedStage ?? stageResult.stage;
1970
- break;
831
+ resolved[key] = value;
832
+ }
833
+ return resolved;
834
+ }
835
+ function extractRequestBodySchema(operationSchema) {
836
+ const requestBody = operationSchema.requestBody;
837
+ if (!isRecord(requestBody)) return void 0;
838
+ const content = requestBody.content;
839
+ if (!isRecord(content)) return void 0;
840
+ const jsonMediaType = content["application/json"];
841
+ if (isRecord(jsonMediaType) && isRecord(jsonMediaType.schema)) {
842
+ return jsonMediaType.schema;
843
+ }
844
+ for (const mediaType of Object.values(content)) {
845
+ if (isRecord(mediaType) && isRecord(mediaType.schema)) {
846
+ return mediaType.schema;
1971
847
  }
1972
848
  }
1973
- if (merged.size === 0) {
1974
- warnings.push(
1975
- warning(
1976
- "NO_DISCOVERY_SOURCES",
1977
- "error",
1978
- "No discovery stage returned first-valid-non-empty results."
1979
- )
1980
- );
1981
- }
1982
- const dedupedWarnings = dedupeWarnings(warnings);
1983
- const upgradeSignal = computeUpgradeSignal(dedupedWarnings);
1984
- const resources = [...merged.values()];
1985
- if (!detailed) {
1986
- return {
1987
- origin,
1988
- resources,
1989
- warnings: dedupedWarnings,
1990
- compatMode,
1991
- ...upgradeSignal,
1992
- ...selectedStage ? { selectedStage } : {}
1993
- };
1994
- }
849
+ return void 0;
850
+ }
851
+ function extractParameters(operationSchema) {
852
+ const params = operationSchema.parameters;
853
+ if (!Array.isArray(params)) return [];
854
+ return params.filter((value) => isRecord(value));
855
+ }
856
+ function extractInputSchema(operationSchema) {
857
+ const requestBody = extractRequestBodySchema(operationSchema);
858
+ const parameters = extractParameters(operationSchema);
859
+ if (!requestBody && parameters.length === 0) return void 0;
860
+ if (requestBody && parameters.length === 0) return requestBody;
1995
861
  return {
1996
- origin,
1997
- resources,
1998
- warnings: dedupedWarnings,
1999
- compatMode,
2000
- ...upgradeSignal,
2001
- selectedStage: trace.find((entry) => entry.valid)?.stage,
2002
- trace,
2003
- resourceWarnings,
2004
- ...includeRaw ? { rawSources } : {}
862
+ ...requestBody ? { requestBody } : {},
863
+ ...parameters.length > 0 ? { parameters } : {}
2005
864
  };
2006
865
  }
2007
-
2008
- // src/validation/payment-required.ts
2009
- import { parsePaymentRequired } from "@x402/core/schemas";
2010
-
2011
- // src/harness.ts
2012
- var INTENT_TRIGGERS = ["x402", "mpp", "pay for", "micropayment", "agentic commerce"];
2013
- var CLIENT_PROFILES = {
2014
- "claude-code": {
2015
- id: "claude-code",
2016
- label: "Claude Code MCP Harness",
2017
- surface: "mcp",
2018
- defaultContextWindowTokens: 2e5,
2019
- zeroHopBudgetPercent: 0.1,
2020
- notes: "Targets environments where MCP context can use roughly 10% of total context."
2021
- },
2022
- "skill-cli": {
2023
- id: "skill-cli",
2024
- label: "Skill + CLI Harness",
2025
- surface: "skill-cli",
2026
- defaultContextWindowTokens: 2e5,
2027
- zeroHopBudgetPercent: 0.02,
2028
- notes: "Targets constrained skill contexts with title/description-heavy routing."
2029
- },
2030
- "generic-mcp": {
2031
- id: "generic-mcp",
2032
- label: "Generic MCP Harness",
2033
- surface: "mcp",
2034
- defaultContextWindowTokens: 128e3,
2035
- zeroHopBudgetPercent: 0.05,
2036
- notes: "Conservative MCP profile when specific client budgets are unknown."
2037
- },
2038
- generic: {
2039
- id: "generic",
2040
- label: "Generic Agent Harness",
2041
- surface: "generic",
2042
- defaultContextWindowTokens: 128e3,
2043
- zeroHopBudgetPercent: 0.03,
2044
- notes: "Fallback profile for unknown clients."
866
+ function parseOperationPrice(operation) {
867
+ const paymentInfo = operation["x-payment-info"];
868
+ if (!isRecord(paymentInfo)) return void 0;
869
+ function toFiniteNumber(value) {
870
+ if (typeof value === "number" && Number.isFinite(value)) return value;
871
+ if (typeof value === "string" && value.trim().length > 0) {
872
+ const parsed = Number(value);
873
+ if (Number.isFinite(parsed)) return parsed;
874
+ }
875
+ return void 0;
2045
876
  }
2046
- };
2047
- function isFirstPartyDomain(hostname) {
2048
- const normalized = hostname.toLowerCase();
2049
- return normalized.endsWith(".dev") && normalized.startsWith("stable");
877
+ const fixed = toFiniteNumber(paymentInfo.price);
878
+ if (fixed != null) return `$${String(fixed)}`;
879
+ const min = toFiniteNumber(paymentInfo.minPrice);
880
+ const max = toFiniteNumber(paymentInfo.maxPrice);
881
+ if (min != null && max != null) return `$${String(min)}-$${String(max)}`;
882
+ return void 0;
2050
883
  }
2051
- function safeHostname(origin) {
2052
- try {
2053
- return new URL(origin).hostname;
2054
- } catch {
2055
- return origin.replace(/^https?:\/\//, "").split("/")[0] ?? origin;
2056
- }
884
+ function parseOperationProtocols(operation) {
885
+ const paymentInfo = operation["x-payment-info"];
886
+ if (!isRecord(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
887
+ const protocols = paymentInfo.protocols.filter(
888
+ (protocol) => typeof protocol === "string" && protocol.length > 0 && (protocol !== "mpp" || isMmmEnabled())
889
+ );
890
+ return protocols.length > 0 ? protocols : void 0;
891
+ }
892
+ function getL3ForOpenAPI(openApi, path, method) {
893
+ const document = openApi.raw;
894
+ const paths = isRecord(document.paths) ? document.paths : void 0;
895
+ if (!paths) return null;
896
+ const matched = findMatchingOpenApiPath(paths, path);
897
+ if (!matched) return null;
898
+ const operation = matched.pathItem[method.toLowerCase()];
899
+ if (!isRecord(operation)) return null;
900
+ const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
901
+ const summary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
902
+ return {
903
+ source: "openapi",
904
+ ...summary ? { summary } : {},
905
+ authMode: inferAuthMode(resolvedOperation) ?? void 0,
906
+ estimatedPrice: parseOperationPrice(resolvedOperation),
907
+ protocols: parseOperationProtocols(resolvedOperation),
908
+ inputSchema: extractInputSchema(resolvedOperation),
909
+ outputSchema: resolvedOperation
910
+ };
2057
911
  }
2058
- function previewText(text) {
2059
- if (!text) return null;
2060
- const compact = text.replace(/\s+/g, " ").trim();
2061
- if (!compact) return null;
2062
- return compact.length > 220 ? `${compact.slice(0, 217)}...` : compact;
2063
- }
2064
- function toAuthModeCount(resources) {
2065
- const counts = {
2066
- paid: 0,
2067
- siwx: 0,
2068
- apiKey: 0,
2069
- unprotected: 0,
2070
- unknown: 0
912
+ function getL3ForProbe(probe, path, method) {
913
+ const probeResult = probe.find((r) => r.path === path && r.method === method);
914
+ if (!probeResult) return null;
915
+ const inputSchema = probeResult.paymentRequiredBody ? parseInputSchema(probeResult.paymentRequiredBody) : void 0;
916
+ const outputSchema = probeResult.paymentRequiredBody ? parseOutputSchema(probeResult.paymentRequiredBody) : void 0;
917
+ const paymentOptions = [
918
+ ...probeResult.paymentRequiredBody ? extractPaymentOptions3(probeResult.paymentRequiredBody) : [],
919
+ ...isMmmEnabled() ? extractPaymentOptions4(probeResult.wwwAuthenticate) : []
920
+ // isMmmEnabled
921
+ ];
922
+ return {
923
+ source: "probe",
924
+ authMode: probeResult.authHint,
925
+ ...probeResult.protocols?.length ? { protocols: probeResult.protocols } : {},
926
+ ...inputSchema ? { inputSchema } : {},
927
+ ...outputSchema ? { outputSchema } : {},
928
+ ...paymentOptions.length ? { paymentOptions } : {}
2071
929
  };
2072
- for (const resource of resources) {
2073
- const mode = resource.authHint;
2074
- if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
2075
- counts[mode] += 1;
2076
- } else {
2077
- counts.unknown += 1;
2078
- }
2079
- }
2080
- return counts;
2081
930
  }
2082
- function toSourceCount(resources) {
2083
- return resources.reduce(
2084
- (acc, resource) => {
2085
- acc[resource.source] = (acc[resource.source] ?? 0) + 1;
2086
- return acc;
2087
- },
2088
- {}
2089
- );
931
+
932
+ // src/runtime/check-endpoint.ts
933
+ function getAdvisoriesForOpenAPI(openApi, path) {
934
+ return [...HTTP_METHODS].flatMap((method) => {
935
+ const l3 = getL3ForOpenAPI(openApi, path, method);
936
+ return l3 ? [{ method, ...l3 }] : [];
937
+ });
2090
938
  }
2091
- function toL2Entries(resources) {
2092
- return [...resources].sort((a, b) => {
2093
- if (a.path !== b.path) return a.path.localeCompare(b.path);
2094
- if (a.method !== b.method) return a.method.localeCompare(b.method);
2095
- return a.resourceKey.localeCompare(b.resourceKey);
2096
- }).map((resource) => ({
2097
- resourceKey: resource.resourceKey,
2098
- method: resource.method,
2099
- path: resource.path,
2100
- source: resource.source,
2101
- authMode: resource.authHint ?? null
2102
- }));
939
+ function getAdvisoriesForProbe(probe, path) {
940
+ return [...HTTP_METHODS].flatMap((method) => {
941
+ const l3 = getL3ForProbe(probe, path, method);
942
+ return l3 ? [{ method, ...l3 }] : [];
943
+ });
2103
944
  }
2104
- function getLlmsTxtInfo(result) {
2105
- const llmsTxtUrl = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? null;
2106
- const llmsTxt = result.rawSources?.llmsTxt;
2107
- if (llmsTxt) {
945
+ async function checkEndpointSchema(options) {
946
+ const endpoint = new URL(options.url);
947
+ const origin = normalizeOrigin(endpoint.origin);
948
+ const path = normalizePath(endpoint.pathname || "/");
949
+ if (options.sampleInputBody !== void 0) {
950
+ const probeResult2 = await getProbe(
951
+ options.url,
952
+ options.headers,
953
+ options.signal,
954
+ options.sampleInputBody
955
+ );
956
+ if (probeResult2.isErr()) {
957
+ return {
958
+ found: false,
959
+ origin,
960
+ path,
961
+ cause: probeResult2.error.cause,
962
+ message: probeResult2.error.message
963
+ };
964
+ }
965
+ const advisories2 = getAdvisoriesForProbe(probeResult2.value, path);
966
+ if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
967
+ return { found: false, origin, path, cause: "not_found" };
968
+ }
969
+ const openApiResult = await getOpenAPI(origin, options.headers, options.signal);
970
+ const openApi = openApiResult.isOk() ? openApiResult.value : null;
971
+ if (openApi) {
972
+ const advisories2 = getAdvisoriesForOpenAPI(openApi, path);
973
+ if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
974
+ return { found: false, origin, path, cause: "not_found" };
975
+ }
976
+ const probeResult = await getProbe(options.url, options.headers, options.signal);
977
+ if (probeResult.isErr()) {
2108
978
  return {
2109
- llmsTxtUrl,
2110
- llmsTxtTokenEstimate: estimateTokenCount(llmsTxt),
2111
- guidancePreview: previewText(llmsTxt),
2112
- guidanceStatus: "present"
979
+ found: false,
980
+ origin,
981
+ path,
982
+ cause: probeResult.error.cause,
983
+ message: probeResult.error.message
2113
984
  };
2114
985
  }
2115
- if (llmsTxtUrl) {
2116
- const failed = result.warnings.some((warning2) => warning2.code === "LLMSTXT_FETCH_FAILED");
2117
- return {
2118
- llmsTxtUrl,
2119
- llmsTxtTokenEstimate: 0,
2120
- guidancePreview: null,
2121
- guidanceStatus: failed ? "advertised_but_unfetched" : "missing"
2122
- };
986
+ const advisories = getAdvisoriesForProbe(probeResult.value, path);
987
+ if (advisories.length > 0) return { found: true, origin, path, advisories };
988
+ return { found: false, origin, path, cause: "not_found" };
989
+ }
990
+
991
+ // src/cli.ts
992
+ function parseArgs(args) {
993
+ const flags = { json: false, verbose: false };
994
+ const positional = [];
995
+ for (const arg of args) {
996
+ if (arg === "--json") flags.json = true;
997
+ else if (arg === "-v" || arg === "--verbose") flags.verbose = true;
998
+ else positional.push(arg);
2123
999
  }
2124
- return {
2125
- llmsTxtUrl: null,
2126
- llmsTxtTokenEstimate: 0,
2127
- guidancePreview: null,
2128
- guidanceStatus: "not_advertised"
2129
- };
1000
+ const [first, second] = positional;
1001
+ if (first && (first.startsWith("http://") || first.startsWith("https://"))) {
1002
+ return { command: "discover", target: first, flags };
1003
+ }
1004
+ return { command: first ?? null, target: second ?? null, flags };
2130
1005
  }
2131
- function getHarnessClientProfile(client = "claude-code") {
2132
- return CLIENT_PROFILES[client] ?? CLIENT_PROFILES.generic;
2133
- }
2134
- function buildContextHarnessReport(options) {
2135
- const client = getHarnessClientProfile(options.client);
2136
- const contextWindowTokens = options.contextWindowTokens ?? client.defaultContextWindowTokens;
2137
- const zeroHopBudgetTokens = Math.floor(contextWindowTokens * client.zeroHopBudgetPercent);
2138
- const hostname = safeHostname(options.result.origin);
2139
- const domainClass = isFirstPartyDomain(hostname) ? "first-party" : "ugc";
2140
- const l0Summary = "Route payment-capable intents to agentcash and use install/discover commands for progressive disclosure.";
2141
- const l1Summary = "Expose installed domain routing hints and trigger fan-out into L2/L3 commands when user intent matches domain capabilities.";
2142
- const l0EstimatedTokens = estimateTokenCount(
2143
- `${INTENT_TRIGGERS.join(", ")} npx agentcash install npx agentcash install-ext`
2144
- );
2145
- const l1EstimatedTokens = estimateTokenCount(
2146
- `${hostname} ${l1Summary} npx agentcash discover ${hostname} npx agentcash discover ${hostname} --verbose`
2147
- );
2148
- const llmsInfo = getLlmsTxtInfo(options.result);
1006
+ function routeToResource(route) {
2149
1007
  return {
2150
- target: options.target,
2151
- origin: options.result.origin,
2152
- client,
2153
- budget: {
2154
- contextWindowTokens,
2155
- zeroHopBudgetTokens,
2156
- estimatedZeroHopTokens: l0EstimatedTokens + l1EstimatedTokens,
2157
- withinBudget: l0EstimatedTokens + l1EstimatedTokens <= zeroHopBudgetTokens
2158
- },
2159
- levels: {
2160
- l0: {
2161
- layer: "L0",
2162
- intentTriggers: INTENT_TRIGGERS,
2163
- installCommand: "npx agentcash install",
2164
- deliverySurfaces: ["MCP", "Skill+CLI"],
2165
- summary: l0Summary,
2166
- estimatedTokens: l0EstimatedTokens
2167
- },
2168
- l1: {
2169
- layer: "L1",
2170
- domain: hostname,
2171
- domainClass,
2172
- selectedDiscoveryStage: options.result.selectedStage ?? null,
2173
- summary: l1Summary,
2174
- fanoutCommands: [
2175
- `npx agentcash discover ${hostname}`,
2176
- `npx agentcash discover ${hostname} --verbose`
2177
- ],
2178
- estimatedTokens: l1EstimatedTokens
2179
- },
2180
- l2: {
2181
- layer: "L2",
2182
- command: `npx agentcash discover ${hostname}`,
2183
- tokenLight: true,
2184
- resourceCount: options.result.resources.length,
2185
- resources: toL2Entries(options.result.resources)
2186
- },
2187
- l3: {
2188
- layer: "L3",
2189
- command: `npx agentcash discover ${hostname} --verbose`,
2190
- detailLevel: "endpoint-schema-and-metadata",
2191
- countsByAuthMode: toAuthModeCount(options.result.resources),
2192
- countsBySource: toSourceCount(options.result.resources),
2193
- pricedResourceCount: options.result.resources.filter(
2194
- (resource) => resource.authHint === "paid"
2195
- ).length
2196
- },
2197
- l4: {
2198
- layer: "L4",
2199
- guidanceSource: llmsInfo.llmsTxtUrl ? "llms.txt" : "none",
2200
- llmsTxtUrl: llmsInfo.llmsTxtUrl,
2201
- llmsTxtTokenEstimate: llmsInfo.llmsTxtTokenEstimate,
2202
- guidancePreview: llmsInfo.guidancePreview,
2203
- guidanceStatus: llmsInfo.guidanceStatus
2204
- },
2205
- l5: {
2206
- layer: "L5",
2207
- status: "out_of_scope",
2208
- note: "Cross-domain composition is intentionally out of scope for discovery v1."
2209
- }
2210
- },
2211
- diagnostics: {
2212
- warningCount: options.result.warnings.length,
2213
- errorWarningCount: options.result.warnings.filter((warning2) => warning2.severity === "error").length,
2214
- selectedStage: options.result.selectedStage ?? null,
2215
- upgradeSuggested: options.result.upgradeSuggested,
2216
- upgradeReasons: options.result.upgradeReasons
2217
- }
1008
+ resourceKey: `${route.method} ${route.path}`,
1009
+ method: route.method,
1010
+ path: route.path,
1011
+ summary: route.summary,
1012
+ ...route.authMode ? { authHint: route.authMode } : {},
1013
+ ...route.price ? { priceHint: route.price } : {},
1014
+ ...route.protocols?.length ? { protocols: route.protocols } : {}
2218
1015
  };
2219
1016
  }
2220
-
2221
- // src/index.ts
2222
- async function discoverDetailed(options) {
2223
- return await runDiscovery({ detailed: true, options });
2224
- }
2225
-
2226
- // src/cli/run.ts
2227
- var CLI_USER_AGENT = "@agentcash/discovery-cli/0.1";
2228
- function buildProbeCandidates(resources) {
2229
- const methodsByPath = /* @__PURE__ */ new Map();
2230
- for (const resource of resources) {
2231
- const existing = methodsByPath.get(resource.path) ?? /* @__PURE__ */ new Set();
2232
- existing.add(resource.method);
2233
- methodsByPath.set(resource.path, existing);
2234
- }
2235
- return [...methodsByPath.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([path, methods]) => ({ path, methods: [...methods].sort() }));
2236
- }
2237
- function createTimeoutFetcher(timeoutMs, fetchImpl) {
2238
- return async (input, init = {}) => {
2239
- const controller = new AbortController();
2240
- const onAbort = () => controller.abort();
2241
- const timer = setTimeout(() => controller.abort(), timeoutMs);
2242
- if (init.signal) {
2243
- if (init.signal.aborted) {
2244
- clearTimeout(timer);
2245
- throw new Error("Request aborted");
1017
+ async function main(args) {
1018
+ const { command, target, flags } = parseArgs(args);
1019
+ if (command === "discover" && target) return runDiscover(target, flags);
1020
+ if (command === "check" && target) return runCheck(target, flags);
1021
+ console.log("Usage:");
1022
+ console.log(" discovery <origin> Discover all endpoints at an origin");
1023
+ console.log(" discovery discover <origin> Discover all endpoints at an origin");
1024
+ console.log(" discovery check <url> Inspect a specific endpoint URL");
1025
+ console.log("");
1026
+ console.log("Flags:");
1027
+ console.log(" --json Machine-readable JSON output");
1028
+ console.log(" -v Verbose output (includes guidance text and warning hints)");
1029
+ return 1;
1030
+ }
1031
+ async function runDiscover(target, flags) {
1032
+ const origin = normalizeOrigin(target);
1033
+ if (!flags.json) console.log(`
1034
+ Discovering ${origin}...
1035
+ `);
1036
+ const [openApiResult, wellKnownResult] = await Promise.all([
1037
+ getOpenAPI(origin),
1038
+ getWellKnown(origin)
1039
+ ]);
1040
+ const openApi = openApiResult.isOk() ? openApiResult.value : null;
1041
+ const wellKnown = wellKnownResult.isOk() ? wellKnownResult.value : null;
1042
+ const warnings = [
1043
+ ...getWarningsForOpenAPI(openApi),
1044
+ ...getWarningsForWellKnown(wellKnown)
1045
+ ];
1046
+ if (openApi) {
1047
+ const l2 = checkL2ForOpenAPI(openApi);
1048
+ const l4 = checkL4ForOpenAPI(openApi);
1049
+ warnings.push(...getWarningsForL2(l2), ...getWarningsForL4(l4));
1050
+ if (flags.json) {
1051
+ const meta = {
1052
+ origin,
1053
+ specUrl: openApi.fetchedUrl,
1054
+ ...l2.title ? { title: l2.title } : {},
1055
+ ...l2.description ? { description: l2.description } : {}
1056
+ };
1057
+ if (l4) {
1058
+ meta.guidanceTokens = Math.ceil(l4.guidance.length / 4);
1059
+ if (flags.verbose) meta.guidance = l4.guidance;
2246
1060
  }
2247
- init.signal.addEventListener("abort", onAbort, { once: true });
1061
+ console.log(
1062
+ JSON.stringify(
1063
+ {
1064
+ ok: true,
1065
+ selectedStage: "openapi",
1066
+ resources: l2.routes.map(routeToResource),
1067
+ warnings,
1068
+ trace: [],
1069
+ meta
1070
+ },
1071
+ null,
1072
+ 2
1073
+ )
1074
+ );
1075
+ return 0;
2248
1076
  }
2249
- try {
2250
- return await fetchImpl(input, {
2251
- ...init,
2252
- signal: controller.signal
2253
- });
2254
- } finally {
2255
- clearTimeout(timer);
2256
- init.signal?.removeEventListener("abort", onAbort);
1077
+ console.log(`Source: openapi`);
1078
+ console.log(`Spec: ${openApi.fetchedUrl}`);
1079
+ if (l2.title) console.log(`API: ${l2.title}`);
1080
+ console.log(`Routes: ${l2.routes.length}
1081
+ `);
1082
+ for (const route of l2.routes) {
1083
+ const auth = route.authMode ? ` ${route.authMode}` : "";
1084
+ const price = route.price ? ` ${route.price}` : "";
1085
+ const protocols = route.protocols?.length ? ` [${route.protocols.join(", ")}]` : "";
1086
+ console.log(` ${route.method.padEnd(7)} ${route.path}${auth}${price}${protocols}`);
2257
1087
  }
2258
- };
2259
- }
2260
- async function runAudit(options, deps = {}) {
2261
- const discoverDetailedFn = deps.discoverDetailedFn ?? discoverDetailed;
2262
- const fetchImpl = deps.fetchImpl ?? fetch;
2263
- const fetcher = createTimeoutFetcher(options.timeoutMs, fetchImpl);
2264
- const baseOptions = {
2265
- target: options.target,
2266
- compatMode: options.compatMode,
2267
- fetcher,
2268
- headers: {
2269
- "user-agent": CLI_USER_AGENT
2270
- },
2271
- rawView: options.harness ? "full" : "none"
2272
- };
2273
- const startedAt = Date.now();
2274
- let result = await discoverDetailedFn(baseOptions);
2275
- let probeCandidateCount = 0;
2276
- if (options.probe) {
2277
- const probeCandidates = buildProbeCandidates(result.resources);
2278
- probeCandidateCount = probeCandidates.length;
2279
- if (probeCandidates.length > 0) {
2280
- result = await discoverDetailedFn({
2281
- ...baseOptions,
2282
- probeCandidates
2283
- });
1088
+ if (l4) {
1089
+ const tokens = Math.ceil(l4.guidance.length / 4);
1090
+ console.log(`
1091
+ Guidance: ${tokens} tokens`);
1092
+ if (flags.verbose) console.log(`
1093
+ ${l4.guidance}`);
2284
1094
  }
2285
- }
2286
- return {
2287
- result,
2288
- durationMs: Date.now() - startedAt,
2289
- probeCandidateCount,
2290
- ...options.harness ? {
2291
- harness: buildContextHarnessReport({
2292
- target: options.target,
2293
- result,
2294
- client: options.client,
2295
- contextWindowTokens: options.contextWindowTokens
2296
- })
2297
- } : {}
2298
- };
2299
- }
2300
-
2301
- // src/cli.ts
2302
- function defaultStdout(message) {
2303
- process.stdout.write(`${message}
1095
+ } else if (wellKnown) {
1096
+ const l2 = checkL2ForWellknown(wellKnown);
1097
+ const l4 = checkL4ForWellknown(wellKnown);
1098
+ warnings.push(...getWarningsForL2(l2), ...getWarningsForL4(l4));
1099
+ if (flags.json) {
1100
+ const meta = { origin, specUrl: wellKnown.fetchedUrl };
1101
+ if (l4 && flags.verbose) meta.guidance = l4.guidance;
1102
+ console.log(
1103
+ JSON.stringify(
1104
+ {
1105
+ ok: true,
1106
+ selectedStage: "well-known/x402",
1107
+ resources: l2.routes.map(routeToResource),
1108
+ warnings,
1109
+ trace: [],
1110
+ meta
1111
+ },
1112
+ null,
1113
+ 2
1114
+ )
1115
+ );
1116
+ return 0;
1117
+ }
1118
+ console.log(`Source: well-known/x402`);
1119
+ console.log(`Spec: ${wellKnown.fetchedUrl}`);
1120
+ console.log(`Routes: ${l2.routes.length}
2304
1121
  `);
1122
+ for (const route of l2.routes) {
1123
+ console.log(` ${route.method.padEnd(7)} ${route.path} paid [x402]`);
1124
+ }
1125
+ } else {
1126
+ if (flags.json) {
1127
+ console.log(
1128
+ JSON.stringify(
1129
+ { ok: false, selectedStage: null, resources: [], warnings, trace: [], meta: { origin } },
1130
+ null,
1131
+ 2
1132
+ )
1133
+ );
1134
+ return 0;
1135
+ }
1136
+ console.log("Not found \u2014 no OpenAPI spec or /.well-known/x402 at this origin.");
1137
+ }
1138
+ printWarnings(warnings, flags.verbose);
1139
+ return 0;
2305
1140
  }
2306
- function defaultStderr(message) {
2307
- process.stderr.write(`${message}
1141
+ async function runCheck(url, flags) {
1142
+ if (!flags.json) console.log(`
1143
+ Checking ${url}...
2308
1144
  `);
2309
- }
2310
- async function main(argv = process.argv.slice(2), deps = {}) {
2311
- const stdout = deps.stdout ?? defaultStdout;
2312
- const stderr = deps.stderr ?? defaultStderr;
2313
- const parsed = parseCliArgs(argv);
2314
- if (parsed.kind === "help") {
2315
- stdout(renderHelp());
2316
- return 0;
2317
- }
2318
- if (parsed.kind === "error") {
2319
- stderr(parsed.message);
2320
- stderr("");
2321
- stderr(renderHelp());
2322
- return 2;
1145
+ const result = await checkEndpointSchema({ url });
1146
+ if (!result.found) {
1147
+ const warnings2 = getWarningsForL3(null);
1148
+ if (flags.json) {
1149
+ console.log(JSON.stringify({ url, found: false, warnings: warnings2 }, null, 2));
1150
+ return 1;
1151
+ }
1152
+ console.log("Not found \u2014 no spec data for this endpoint.");
1153
+ printWarnings(warnings2, flags.verbose);
1154
+ return 1;
2323
1155
  }
2324
- try {
2325
- const run = await runAudit(parsed.options, deps);
2326
- if (parsed.options.json) {
2327
- stdout(renderJson(run, parsed.options));
2328
- } else if (parsed.options.verbose) {
2329
- stdout(renderVerbose(run, parsed.options));
2330
- } else {
2331
- stdout(renderSummary(run, parsed.options));
1156
+ const warnings = [];
1157
+ if (flags.json) {
1158
+ for (const advisory of result.advisories) {
1159
+ warnings.push(...getWarningsForL3(advisory));
2332
1160
  }
2333
- return run.result.resources.length > 0 ? 0 : 1;
2334
- } catch (error) {
2335
- const message = error instanceof Error ? error.message : String(error);
2336
- stderr(`Discovery audit failed: ${message}`);
2337
- return 2;
1161
+ console.log(
1162
+ JSON.stringify(
1163
+ {
1164
+ url,
1165
+ found: true,
1166
+ origin: result.origin,
1167
+ path: result.path,
1168
+ advisories: result.advisories,
1169
+ warnings
1170
+ },
1171
+ null,
1172
+ 2
1173
+ )
1174
+ );
1175
+ return 0;
1176
+ }
1177
+ console.log(`Origin: ${result.origin}`);
1178
+ console.log(`Path: ${result.path}
1179
+ `);
1180
+ for (const advisory of result.advisories) {
1181
+ const auth = advisory.authMode ? ` ${advisory.authMode}` : "";
1182
+ const price = advisory.estimatedPrice ? ` ${advisory.estimatedPrice}` : "";
1183
+ const protocols = advisory.protocols?.length ? ` [${advisory.protocols.join(", ")}]` : "";
1184
+ console.log(` ${advisory.method.padEnd(7)}${auth}${price}${protocols}`);
1185
+ warnings.push(...getWarningsForL3(advisory));
1186
+ }
1187
+ printWarnings(warnings, flags.verbose);
1188
+ return 0;
1189
+ }
1190
+ function printWarnings(warnings, verbose) {
1191
+ if (warnings.length === 0) return;
1192
+ console.log(`
1193
+ Warnings (${warnings.length}):`);
1194
+ for (const w of warnings) {
1195
+ const loc = w.path ? ` (${w.path})` : "";
1196
+ const hint = verbose && w.hint ? `
1197
+ Hint: ${w.hint}` : "";
1198
+ console.log(` [${w.severity.padEnd(4)}] ${w.code}${loc}
1199
+ ${w.message}${hint}`);
2338
1200
  }
2339
1201
  }
2340
1202
  export {