agentgrade-cli 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/LICENSE +21 -0
- package/README.md +71 -0
- package/cli.mjs +337 -0
- package/lib/probe.mjs +1383 -0
- package/package.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agentic Adventures
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# agentgrade
|
|
2
|
+
|
|
3
|
+
Scan any website for AI agent readiness.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx agentgrade https://example.com
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No install needed. Runs directly with `npx`.
|
|
12
|
+
|
|
13
|
+
## What It Checks
|
|
14
|
+
|
|
15
|
+
- **Payment protocols** -- x402 (Coinbase), MPP (Machine Payments Protocol), L402 (Lightning), Stripe SPT
|
|
16
|
+
- **Discovery endpoints** -- `/.well-known/mpp.json`, `/.well-known/x402.json`, payment info headers
|
|
17
|
+
- **Bazaar discovery** -- x402 service catalogs, probe endpoints, Link headers
|
|
18
|
+
- **MCP servers** -- Model Context Protocol endpoints and tool counts
|
|
19
|
+
- **Claude plugins** -- `/.well-known/claude-plugin.json`
|
|
20
|
+
- **OpenAI plugins** -- `/.well-known/ai-plugin.json`
|
|
21
|
+
- **OpenAPI specs** -- `/openapi.json`, `/swagger.json`
|
|
22
|
+
- **llms.txt** -- LLM-friendly site descriptions
|
|
23
|
+
- **agents.txt** -- Agent permission declarations
|
|
24
|
+
- **robots.txt** -- AI agent-specific directives
|
|
25
|
+
- **Homepage scan** -- Mentions of payment protocols in page content
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Scan a specific endpoint for 402 payment challenges plus all discovery:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
agentgrade https://api.example.com/v1/submit POST '{"title":"test"}'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Run discovery checks only (skip the 402 probe):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
agentgrade https://example.com --discover-only
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Default method is POST. Pass GET explicitly for read endpoints:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
agentgrade https://api.example.com/v1/feed GET
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Output
|
|
48
|
+
|
|
49
|
+
The scanner prints color-coded terminal output organized into sections:
|
|
50
|
+
|
|
51
|
+
- **402 Probe** -- Sends the request and inspects response headers for x402, MPP, and L402 challenges
|
|
52
|
+
- **Discovery** -- Checks well-known endpoints for payment configuration files
|
|
53
|
+
- **Bazaar Discovery** -- Looks for x402 service catalogs and probe endpoints
|
|
54
|
+
- **Homepage Scan** -- Scans the homepage HTML for protocol mentions
|
|
55
|
+
- **Agent Capabilities** -- Finds MCP servers, plugins, OpenAPI specs, llms.txt, and more
|
|
56
|
+
- **Summary** -- Lists confirmed payment rails, documented protocols, and detected capabilities
|
|
57
|
+
|
|
58
|
+
Green checkmarks indicate detected features. Dimmed X marks indicate features not found.
|
|
59
|
+
|
|
60
|
+
## Web Scanner
|
|
61
|
+
|
|
62
|
+
For a full web-based scan with a visual report, visit [agentgrade.com](https://agentgrade.com).
|
|
63
|
+
|
|
64
|
+
## Links
|
|
65
|
+
|
|
66
|
+
- [AgentGrade web scanner](https://agentgrade.com)
|
|
67
|
+
- [Agentic Adventures](https://github.com/agentic-adventures)
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* agentgrade CLI — scan any site for agent capabilities and payment protocols
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node cli.mjs <url> [method] [body]
|
|
8
|
+
* node cli.mjs <url> --discover-only
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* node cli.mjs https://agentnews.xyz/api/v1/submit POST '{"title":"test"}'
|
|
12
|
+
* node cli.mjs https://agentnews.xyz --discover-only
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { probe402, probeDiscovery, probeBazaar, probeHomepage, probeCapabilities } from './lib/probe.mjs';
|
|
16
|
+
|
|
17
|
+
const BOLD = '\x1b[1m';
|
|
18
|
+
const DIM = '\x1b[2m';
|
|
19
|
+
const GREEN = '\x1b[32m';
|
|
20
|
+
const YELLOW = '\x1b[33m';
|
|
21
|
+
const CYAN = '\x1b[36m';
|
|
22
|
+
const RED = '\x1b[31m';
|
|
23
|
+
const RESET = '\x1b[0m';
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2).filter(a => !a.startsWith('--'));
|
|
26
|
+
const flags = new Set(process.argv.slice(2).filter(a => a.startsWith('--')));
|
|
27
|
+
const discoverOnly = flags.has('--discover-only');
|
|
28
|
+
|
|
29
|
+
if (args.length === 0) {
|
|
30
|
+
console.log(`\n${BOLD}agentgrade${RESET} — scan any site for agent capabilities\n`);
|
|
31
|
+
console.log(`Usage:`);
|
|
32
|
+
console.log(` node cli.mjs <url> [method] [body]`);
|
|
33
|
+
console.log(` node cli.mjs <url> --discover-only\n`);
|
|
34
|
+
console.log(`Examples:`);
|
|
35
|
+
console.log(` node cli.mjs https://agentnews.xyz/api/v1/submit POST '{"title":"test"}'`);
|
|
36
|
+
console.log(` node cli.mjs https://agentnews.xyz --discover-only\n`);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const url = new URL(args[0]);
|
|
41
|
+
const method = (args[1] || 'POST').toUpperCase();
|
|
42
|
+
const body = args[2] || undefined;
|
|
43
|
+
const origin = url.origin;
|
|
44
|
+
const base = origin + url.pathname.replace(/\/$/, '');
|
|
45
|
+
|
|
46
|
+
// ─── Formatting helpers ────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
function section(title) { console.log(`\n${BOLD}${title}${RESET}`); }
|
|
49
|
+
function found(protocol, details) { console.log(` ${GREEN}✓ ${protocol}${RESET} ${details}`); }
|
|
50
|
+
function notFound(protocol, reason) { console.log(` ${DIM}✗ ${protocol} ${reason}${RESET}`); }
|
|
51
|
+
function info(label, value) { console.log(` ${CYAN}${label}:${RESET} ${value}`); }
|
|
52
|
+
function warn(msg) { console.log(` ${YELLOW}${msg}${RESET}`); }
|
|
53
|
+
|
|
54
|
+
// ─── 402 Probe ──────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
const rails = [];
|
|
57
|
+
let cfBlocked = false;
|
|
58
|
+
|
|
59
|
+
if (!discoverOnly) {
|
|
60
|
+
section(`Probing ${method} ${url.href}`);
|
|
61
|
+
|
|
62
|
+
const result = await probe402(url.href, method, body);
|
|
63
|
+
info('Status', `${result.status}${result.error ? ` (${result.error})` : ''}`);
|
|
64
|
+
|
|
65
|
+
if (result.status !== 402) {
|
|
66
|
+
if (result.cfBlocked) {
|
|
67
|
+
cfBlocked = true;
|
|
68
|
+
console.log(` ${RED}Cloudflare blocked${RESET} — bot challenge intercepted the request. Agents cannot reach this endpoint.`);
|
|
69
|
+
} else if (result.status >= 200 && result.status < 300) {
|
|
70
|
+
warn(`No payment required (${result.status}) — may use quota-based 402`);
|
|
71
|
+
} else if (result.error) {
|
|
72
|
+
console.log(` ${RED}Error:${RESET} ${result.error}`);
|
|
73
|
+
} else {
|
|
74
|
+
warn(`Got ${result.status} — not a 402. Try a different method or path.`);
|
|
75
|
+
}
|
|
76
|
+
if (result.bodyMentions.length > 0) {
|
|
77
|
+
found('Body scan', `mentions: ${result.bodyMentions.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// x402
|
|
81
|
+
if (result.x402) {
|
|
82
|
+
if (result.x402.decodeFailed) {
|
|
83
|
+
found('x402', `Payment-Required header present (could not decode)`);
|
|
84
|
+
info(' Raw', result.x402.raw);
|
|
85
|
+
} else {
|
|
86
|
+
found('x402', `V${result.x402.version} — ${result.x402.options.length} payment option(s)`);
|
|
87
|
+
if (result.x402.description) info(' Description', result.x402.description);
|
|
88
|
+
for (const opt of result.x402.options) {
|
|
89
|
+
info(' Network', opt.network);
|
|
90
|
+
info(' Amount', `${opt.amount}${opt.assetName ? ` (${opt.assetName})` : ''}`);
|
|
91
|
+
info(' Asset', opt.asset);
|
|
92
|
+
info(' Pay to', opt.payTo);
|
|
93
|
+
if (opt.scheme) info(' Scheme', opt.scheme);
|
|
94
|
+
if (opt.maxTimeoutSeconds) info(' Timeout', `${opt.maxTimeoutSeconds}s`);
|
|
95
|
+
console.log();
|
|
96
|
+
}
|
|
97
|
+
if (result.x402.extensions) {
|
|
98
|
+
info(' Extensions', Object.keys(result.x402.extensions).join(', '));
|
|
99
|
+
if (result.x402.bazaar) {
|
|
100
|
+
const bz = result.x402.bazaar;
|
|
101
|
+
found(' Bazaar', `discoverable=${bz.discoverable ?? '?'}`);
|
|
102
|
+
if (bz.description) info(' Description', bz.description);
|
|
103
|
+
if (bz.inputSchema) info(' Input schema', JSON.stringify(bz.inputSchema));
|
|
104
|
+
if (bz.outputSchema) info(' Output schema', JSON.stringify(bz.outputSchema));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
rails.push('x402');
|
|
109
|
+
} else {
|
|
110
|
+
notFound('x402', 'no Payment-Required header');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// MPP
|
|
114
|
+
if (result.mpp) {
|
|
115
|
+
found('MPP', 'WWW-Authenticate: Payment');
|
|
116
|
+
for (const [k, v] of Object.entries(result.mpp.fields)) info(` ${k}`, v);
|
|
117
|
+
rails.push('MPP');
|
|
118
|
+
} else {
|
|
119
|
+
notFound('MPP', 'no WWW-Authenticate: Payment header');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// L402
|
|
123
|
+
if (result.l402) {
|
|
124
|
+
found('L402', `WWW-Authenticate: ${result.l402.header}`);
|
|
125
|
+
if (result.l402.macaroon) info(' Macaroon', result.l402.macaroon.slice(0, 60) + '...');
|
|
126
|
+
if (result.l402.invoice) info(' Invoice', result.l402.invoice.slice(0, 60) + '...');
|
|
127
|
+
rails.push('L402');
|
|
128
|
+
} else {
|
|
129
|
+
notFound('L402', 'no L402/LSAT in WWW-Authenticate');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Other headers
|
|
133
|
+
if (result.otherHeaders.length > 0) {
|
|
134
|
+
console.log(`\n ${DIM}Other payment-related headers:${RESET}`);
|
|
135
|
+
for (const h of result.otherHeaders) info(` ${h.name}`, h.value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ─── Discovery ──────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
section(`Discovery (${origin})`);
|
|
143
|
+
|
|
144
|
+
const discovery = await probeDiscovery(origin);
|
|
145
|
+
for (const ep of discovery.endpoints) {
|
|
146
|
+
if (ep.hasPayment || ep.mentions.length > 0) {
|
|
147
|
+
const detail = ep.mentions.length > 0 ? `mentions: ${ep.mentions.join(', ')}` : 'payment info found';
|
|
148
|
+
found(ep.path, `${ep.status} — ${detail}`);
|
|
149
|
+
if (ep.settlementRails) {
|
|
150
|
+
for (const rail of ep.settlementRails) {
|
|
151
|
+
info(` ${rail.name}`, `${rail.protocol} — ${rail.currency} (${rail.status})`);
|
|
152
|
+
if (!rails.includes(rail.protocol)) rails.push(rail.protocol);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (ep.paymentMode) info(' payment_mode', ep.paymentMode);
|
|
156
|
+
if (ep.paymentInfo) info(' payment_info', ep.paymentInfo);
|
|
157
|
+
} else {
|
|
158
|
+
info(ep.path, `${ep.status} (no payment info)`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── Bazaar Discovery ───────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
section(`Bazaar Discovery (${origin})`);
|
|
165
|
+
|
|
166
|
+
const bazaar = await probeBazaar(origin);
|
|
167
|
+
|
|
168
|
+
if (bazaar.x402Json) {
|
|
169
|
+
found('/.well-known/x402.json', 'x402 service catalog found');
|
|
170
|
+
const bf = bazaar.x402Json.fields || {};
|
|
171
|
+
const showField = (label, key) => {
|
|
172
|
+
const d = bf[key];
|
|
173
|
+
if (!d || d.status === 'ok') { if (bazaar.x402Json[key]) info(` ${label}`, bazaar.x402Json[key]); return; }
|
|
174
|
+
if (d.status === 'misnamed') warn(` ${label}: ${d.hint}`);
|
|
175
|
+
else if (d.status === 'empty') warn(` ${label}: ${d.hint}`);
|
|
176
|
+
else warn(` ${label}: ✗ ${d.hint}`);
|
|
177
|
+
};
|
|
178
|
+
showField('Provider', 'name');
|
|
179
|
+
showField('Network', 'network');
|
|
180
|
+
showField('Facilitator', 'facilitator');
|
|
181
|
+
showField('Pay to', 'payTo');
|
|
182
|
+
if (bazaar.x402Json.testnet) warn(' ⚠ Testnet mode');
|
|
183
|
+
if (bazaar.x402Json.services.length > 0) {
|
|
184
|
+
info(' Services', `${bazaar.x402Json.services.length} paid endpoint(s)`);
|
|
185
|
+
for (const svc of bazaar.x402Json.services) {
|
|
186
|
+
const bazaarTag = svc.bazaar?.discoverable ? ` ${GREEN}[Bazaar: discoverable]${RESET}` : '';
|
|
187
|
+
info(` ${svc.method} ${svc.path}`, `${svc.amount}${bazaarTag}`);
|
|
188
|
+
if (svc.description) info(' ', svc.description);
|
|
189
|
+
if (svc.bazaar) {
|
|
190
|
+
if (svc.bazaar.inputSchema) info(' Input', JSON.stringify(svc.bazaar.inputSchema));
|
|
191
|
+
if (svc.bazaar.outputSchema) info(' Output', JSON.stringify(svc.bazaar.outputSchema));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (bazaar.x402Json.freeEndpoints.length > 0) {
|
|
196
|
+
info(' Free endpoints', `${bazaar.x402Json.freeEndpoints.length}`);
|
|
197
|
+
for (const ep of bazaar.x402Json.freeEndpoints) {
|
|
198
|
+
info(` ${ep.method} ${ep.path}`, ep.description);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!rails.includes('x402')) rails.push('x402');
|
|
202
|
+
} else {
|
|
203
|
+
notFound('/.well-known/x402.json', 'not found');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (bazaar.x402Probe) {
|
|
207
|
+
found('/.well-known/x402-probe', 'returns 402 — live challenges');
|
|
208
|
+
if (bazaar.x402Probe.version) info(' Payment-Required', `x402 V${bazaar.x402Probe.version}`);
|
|
209
|
+
if (bazaar.x402Probe.headerBazaar) found(' Bazaar in header', `discoverable=${bazaar.x402Probe.headerBazaar.discoverable}`);
|
|
210
|
+
if (bazaar.x402Probe.challenges.length > 0) {
|
|
211
|
+
info(' Challenges', `${bazaar.x402Probe.challenges.length} endpoint(s)`);
|
|
212
|
+
for (const ch of bazaar.x402Probe.challenges) {
|
|
213
|
+
const tag = ch.bazaar ? ` ${GREEN}[Bazaar]${RESET}` : '';
|
|
214
|
+
info(` ${ch.endpoint}`, `${ch.amount} ${ch.assetName || ''}${tag}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (!rails.includes('x402')) rails.push('x402');
|
|
218
|
+
} else {
|
|
219
|
+
notFound('/.well-known/x402-probe', 'not found');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (bazaar.linkHeader) {
|
|
223
|
+
found('Link header', bazaar.linkHeader);
|
|
224
|
+
} else {
|
|
225
|
+
notFound('Link header', 'no x402 discovery link');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─── Homepage Scan ──────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
if (url.pathname === '/' || discoverOnly) {
|
|
231
|
+
section(`Homepage scan (${origin})`);
|
|
232
|
+
const homepage = await probeHomepage(origin);
|
|
233
|
+
const keys = Object.keys(homepage.protocols);
|
|
234
|
+
if (keys.length > 0) {
|
|
235
|
+
for (const [proto, hints] of Object.entries(homepage.protocols)) {
|
|
236
|
+
const detail = hints.length > 0 ? ` (${hints.join(', ')})` : '';
|
|
237
|
+
found('Homepage', `mentions ${proto}${detail}`);
|
|
238
|
+
if (proto !== '402 flow' && !rails.includes(proto) && !rails.includes(`${proto} (documented)`)) {
|
|
239
|
+
rails.push(`${proto} (documented)`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
notFound('Homepage', 'no payment protocol mentions found');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── Agent Capabilities ────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
section(`Agent Capabilities (${base})`);
|
|
250
|
+
|
|
251
|
+
const rawCapabilities = await probeCapabilities(origin, base);
|
|
252
|
+
const capabilities = rawCapabilities.filter(c => !c.cfBlocked);
|
|
253
|
+
const cfBlockedCaps = rawCapabilities.filter(c => c.cfBlocked);
|
|
254
|
+
const capTypes = new Set();
|
|
255
|
+
|
|
256
|
+
for (const cap of cfBlockedCaps) {
|
|
257
|
+
console.log(` ${RED}✗ ${cap.type}${RESET} ${DIM}blocked by Cloudflare${RESET}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (const cap of capabilities) {
|
|
261
|
+
capTypes.add(cap.type);
|
|
262
|
+
switch (cap.type) {
|
|
263
|
+
case 'MCP':
|
|
264
|
+
found('MCP', `${cap.path} — ${cap.name || cap.detail || 'endpoint found'}`);
|
|
265
|
+
if (cap.tools) info(' Tools', `${cap.tools} tool(s)`);
|
|
266
|
+
if (cap.name) info(' Name', cap.name);
|
|
267
|
+
if (cap.description) info(' Description', cap.description);
|
|
268
|
+
if (cap.transport) info(' Transport', cap.transport);
|
|
269
|
+
if (cap.protocolVersion) info(' Protocol', cap.protocolVersion);
|
|
270
|
+
if (cap.sseSupported) info(' SSE', 'supported');
|
|
271
|
+
if (cap.cors) info(' CORS', 'enabled');
|
|
272
|
+
break;
|
|
273
|
+
case 'Claude Plugin':
|
|
274
|
+
found('Claude Plugin', cap.path);
|
|
275
|
+
if (cap.name) info(' Name', cap.name);
|
|
276
|
+
if (cap.description) info(' Description', cap.description);
|
|
277
|
+
if (cap.version) info(' Version', cap.version);
|
|
278
|
+
if (cap.skills) info(' Skills', `${cap.skills} skill(s)`);
|
|
279
|
+
if (cap.tools) info(' Tools', `${cap.tools} tool(s)`);
|
|
280
|
+
break;
|
|
281
|
+
case 'AI Plugin':
|
|
282
|
+
found('AI Plugin', cap.path);
|
|
283
|
+
if (cap.name) info(' Name', cap.name);
|
|
284
|
+
if (cap.modelName) info(' Model name', cap.modelName);
|
|
285
|
+
if (cap.description) info(' Description', cap.description);
|
|
286
|
+
if (cap.apiUrl) info(' API spec', cap.apiUrl);
|
|
287
|
+
if (cap.auth) info(' Auth', cap.auth);
|
|
288
|
+
break;
|
|
289
|
+
case 'Skills':
|
|
290
|
+
found('Skills', `${cap.path} — ${cap.count ? cap.count + ' skill(s)' : cap.bytes + ' bytes'}`);
|
|
291
|
+
break;
|
|
292
|
+
case 'OpenAPI':
|
|
293
|
+
found('OpenAPI', `${cap.path} — ${cap.version} "${cap.title}" (${cap.paths} paths)`);
|
|
294
|
+
break;
|
|
295
|
+
case 'llms.txt':
|
|
296
|
+
found('llms.txt', `${cap.path} — ${cap.lines} lines`);
|
|
297
|
+
if (cap.preview) info(' ', cap.preview);
|
|
298
|
+
break;
|
|
299
|
+
case 'agents.txt':
|
|
300
|
+
found('agents.txt', `${cap.lines} lines`);
|
|
301
|
+
break;
|
|
302
|
+
case 'robots.txt (agent-aware)':
|
|
303
|
+
found('robots.txt', 'agent-relevant directives found');
|
|
304
|
+
for (const d of cap.directives) info(' ', d);
|
|
305
|
+
break;
|
|
306
|
+
default:
|
|
307
|
+
found(cap.type, cap.path);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Not-found for types we checked but didn't find
|
|
312
|
+
const cfBlockedCapTypes = new Set(cfBlockedCaps.map(c => c.type));
|
|
313
|
+
for (const type of ['MCP', 'Claude Plugin', 'AI Plugin', 'Skills', 'OpenAPI', 'llms.txt', 'agents.txt']) {
|
|
314
|
+
if (!capTypes.has(type) && !cfBlockedCapTypes.has(type)) notFound(type, `no ${type.toLowerCase()} found`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─── Summary ────────────────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
section('Summary');
|
|
320
|
+
|
|
321
|
+
const confirmed = rails.filter(r => !r.includes('(documented)'));
|
|
322
|
+
const documented = rails.filter(r => r.includes('(documented)'));
|
|
323
|
+
if (confirmed.length > 0) console.log(` ${GREEN}${BOLD}Confirmed (402 headers): ${confirmed.join(', ')}${RESET}`);
|
|
324
|
+
if (documented.length > 0) console.log(` ${YELLOW}${BOLD}Documented (mentioned in pages): ${documented.join(', ')}${RESET}`);
|
|
325
|
+
if (bazaar.bazaarServices > 0) {
|
|
326
|
+
console.log(` ${GREEN}${BOLD}Bazaar: ${bazaar.bazaarServices} discoverable service(s)${RESET}`);
|
|
327
|
+
} else {
|
|
328
|
+
console.log(` ${DIM}Bazaar: no discoverable services${RESET}`);
|
|
329
|
+
}
|
|
330
|
+
if (rails.length === 0 && !cfBlocked) console.log(` ${YELLOW}No payment rails detected${RESET}`);
|
|
331
|
+
if (rails.length === 0 && cfBlocked) console.log(` ${RED}No payment rails detected — Cloudflare blocked all probes${RESET}`);
|
|
332
|
+
if (capTypes.size > 0) {
|
|
333
|
+
console.log(` ${GREEN}${BOLD}Agent capabilities: ${[...capTypes].join(', ')}${RESET}`);
|
|
334
|
+
} else {
|
|
335
|
+
console.log(` ${DIM}No agent capabilities detected${RESET}`);
|
|
336
|
+
}
|
|
337
|
+
console.log();
|