@@ -455,6 +455,27 @@ data: ${JSON.stringify({
455455 return
456456 }
457457
458+ if ( request . url === '/skill-catalog.json' ) {
459+ response . setHeader ( 'content-type' , 'application/json' )
460+ response . end ( JSON . stringify ( {
461+ catalog : {
462+ version : '1.0' ,
463+ protocol : 'x402' ,
464+ skills : [ {
465+ id : 'fixture-skill' ,
466+ name : 'Fixture Skill' ,
467+ endpoint : `${ serverUrl } /catalog-skill` ,
468+ method : 'POST' ,
469+ price : { amount : '0.003' , currency : 'USD' , asset : 'USDC' } ,
470+ example : {
471+ input : { url : 'https://example.com/article' } ,
472+ } ,
473+ } ] ,
474+ } ,
475+ } ) )
476+ return
477+ }
478+
458479 if ( request . url === '/string-paid' || request . url === '/string-post' || request . url === '/tool-paid' ) {
459480 response . statusCode = 402
460481 response . setHeader ( 'content-type' , 'application/json' )
@@ -474,6 +495,40 @@ data: ${JSON.stringify({
474495 return
475496 }
476497
498+ if ( request . url === '/catalog-skill' ) {
499+ let body = ''
500+ request . on ( 'data' , chunk => {
501+ body += chunk
502+ } )
503+ request . on ( 'end' , ( ) => {
504+ const parsed = body ? JSON . parse ( body ) : { }
505+ if ( parsed . url !== 'https://example.com/article' ) {
506+ response . statusCode = 400
507+ response . setHeader ( 'content-type' , 'application/json' )
508+ response . end ( JSON . stringify ( { error : 'missing catalog skill example' } ) )
509+ return
510+ }
511+ response . statusCode = 402
512+ response . setHeader ( 'content-type' , 'application/json' )
513+ response . setHeader ( 'access-control-allow-origin' , '*' )
514+ response . setHeader ( 'cache-control' , 'no-store' )
515+ response . end ( JSON . stringify ( {
516+ x402Version : 2 ,
517+ error : 'Payment required' ,
518+ accepts : [ {
519+ scheme : 'exact' ,
520+ network : 'eip155:8453' ,
521+ amount : '3000' ,
522+ asset : '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' ,
523+ payTo : '0x549c82e6bfc54bdae9a2073744cbc2af5d1fc6d1' ,
524+ resource : `${ serverUrl } /catalog-skill` ,
525+ maxTimeoutSeconds : 60 ,
526+ } ] ,
527+ } ) )
528+ } )
529+ return
530+ }
531+
477532 if ( request . url === '/registry-leak.json' ) {
478533 response . setHeader ( 'content-type' , 'application/json' )
479534 response . end ( JSON . stringify ( {
@@ -1383,6 +1438,19 @@ try {
13831438 assert . match ( resourceManifest . stdout , / e i p 1 5 5 : 8 4 5 3 / )
13841439 assert . doesNotMatch ( resourceManifest . stdout , / D o c u m e n t d o e s n o t e x p o s e / )
13851440
1441+ const skillCatalog = await execFileAsync ( 'node' , [
1442+ 'bin/x402-surface-check.mjs' ,
1443+ `${ serverUrl } /skill-catalog.json` ,
1444+ '--origin' ,
1445+ 'https://example.com' ,
1446+ ] , { cwd : new URL ( '..' , import . meta. url ) } )
1447+
1448+ assert . match ( skillCatalog . stdout , / f i x t u r e - s k i l l / )
1449+ assert . match ( skillCatalog . stdout , / c a t a l o g - s k i l l / )
1450+ assert . match ( skillCatalog . stdout , / \$ 0 \. 0 0 3 / )
1451+ assert . doesNotMatch ( skillCatalog . stdout , / v a l i d a t i o n H T T P 4 0 0 / )
1452+ assert . doesNotMatch ( skillCatalog . stdout , / D o c u m e n t d o e s n o t e x p o s e / )
1453+
13861454 const registryLeak = await execFileAsync ( 'node' , [
13871455 'bin/x402-surface-check.mjs' ,
13881456 `${ serverUrl } /registry-leak.json` ,
0 commit comments