@agentcash/discovery 0.1.0 → 0.1.2
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/AGENTS.md +0 -1
- package/README.md +139 -57
- package/dist/cli.cjs +607 -72
- package/dist/cli.js +607 -72
- package/dist/index.cjs +651 -4
- package/dist/index.d.cts +187 -1
- package/dist/index.d.ts +187 -1
- package/dist/index.js +643 -3
- package/package.json +2 -3
- package/.claude/CLAUDE.md +0 -24
package/dist/cli.js
CHANGED
|
@@ -25,6 +25,19 @@ function parseTimeoutMs(value) {
|
|
|
25
25
|
}
|
|
26
26
|
return parsed;
|
|
27
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
|
+
}
|
|
28
41
|
function parseCliArgs(argv) {
|
|
29
42
|
let target;
|
|
30
43
|
let verbose = false;
|
|
@@ -33,6 +46,10 @@ function parseCliArgs(argv) {
|
|
|
33
46
|
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
34
47
|
let compatMode = DEFAULT_COMPAT_MODE;
|
|
35
48
|
let color = true;
|
|
49
|
+
let noTruncate = false;
|
|
50
|
+
let harness = false;
|
|
51
|
+
let client = "claude-code";
|
|
52
|
+
let contextWindowTokens;
|
|
36
53
|
for (let i = 0; i < argv.length; i += 1) {
|
|
37
54
|
const arg = argv[i];
|
|
38
55
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -54,6 +71,14 @@ function parseCliArgs(argv) {
|
|
|
54
71
|
color = false;
|
|
55
72
|
continue;
|
|
56
73
|
}
|
|
74
|
+
if (arg === "--no-truncate") {
|
|
75
|
+
noTruncate = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (arg === "--harness") {
|
|
79
|
+
harness = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
57
82
|
if (arg === "--compat") {
|
|
58
83
|
const value = argv[i + 1];
|
|
59
84
|
if (!value) {
|
|
@@ -86,6 +111,38 @@ function parseCliArgs(argv) {
|
|
|
86
111
|
i += 1;
|
|
87
112
|
continue;
|
|
88
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
|
+
}
|
|
89
146
|
if (arg.startsWith("-")) {
|
|
90
147
|
return { kind: "error", message: `Unknown flag '${arg}'.` };
|
|
91
148
|
}
|
|
@@ -109,7 +166,11 @@ function parseCliArgs(argv) {
|
|
|
109
166
|
compatMode,
|
|
110
167
|
probe,
|
|
111
168
|
timeoutMs,
|
|
112
|
-
color
|
|
169
|
+
color,
|
|
170
|
+
noTruncate,
|
|
171
|
+
harness,
|
|
172
|
+
client,
|
|
173
|
+
...contextWindowTokens ? { contextWindowTokens } : {}
|
|
113
174
|
}
|
|
114
175
|
};
|
|
115
176
|
}
|
|
@@ -122,13 +183,19 @@ function renderHelp() {
|
|
|
122
183
|
" --json Emit JSON output",
|
|
123
184
|
" --compat <mode> Compatibility mode: on | off | strict (default: on)",
|
|
124
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",
|
|
125
190
|
" --timeout-ms <ms> Per-request timeout in milliseconds (default: 5000)",
|
|
126
191
|
" --no-color Disable ANSI colors",
|
|
192
|
+
" --no-truncate Disable output truncation in verbose tables",
|
|
127
193
|
" -h, --help Show help"
|
|
128
194
|
].join("\n");
|
|
129
195
|
}
|
|
130
196
|
|
|
131
197
|
// src/cli/render.ts
|
|
198
|
+
import { getBorderCharacters, table } from "table";
|
|
132
199
|
function createPalette(enabled) {
|
|
133
200
|
const wrap = (code) => (value) => enabled ? `\x1B[${code}m${value}\x1B[0m` : value;
|
|
134
201
|
return {
|
|
@@ -151,7 +218,7 @@ function summarizeWarnings(warnings) {
|
|
|
151
218
|
},
|
|
152
219
|
{}
|
|
153
220
|
);
|
|
154
|
-
const codes = Object.entries(codeCounts).sort((a, b) => b[1] - a[1]).
|
|
221
|
+
const codes = Object.entries(codeCounts).sort((a, b) => b[1] - a[1]).map(([code, count]) => ({ code, count }));
|
|
155
222
|
return {
|
|
156
223
|
total: warnings.length,
|
|
157
224
|
errors,
|
|
@@ -160,53 +227,134 @@ function summarizeWarnings(warnings) {
|
|
|
160
227
|
codes
|
|
161
228
|
};
|
|
162
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
|
+
}
|
|
163
240
|
function formatPricing(resource) {
|
|
164
241
|
if (resource.authHint !== "paid") return "-";
|
|
165
242
|
if (resource.pricing) {
|
|
166
243
|
if (resource.pricing.pricingMode === "fixed") {
|
|
167
|
-
return `fixed
|
|
244
|
+
return resource.pricing.price ? `fixed ${resource.pricing.price}` : "fixed";
|
|
168
245
|
}
|
|
169
246
|
if (resource.pricing.pricingMode === "range") {
|
|
170
|
-
return `range
|
|
247
|
+
return `range ${resource.pricing.minPrice ?? "?"}-${resource.pricing.maxPrice ?? "?"}`;
|
|
171
248
|
}
|
|
172
249
|
return "quote";
|
|
173
250
|
}
|
|
174
|
-
return resource.priceHint
|
|
251
|
+
return resource.priceHint ? `hint ${resource.priceHint}` : "-";
|
|
175
252
|
}
|
|
176
|
-
function
|
|
177
|
-
return
|
|
253
|
+
function statusBadge(ok, palette) {
|
|
254
|
+
return ok ? palette.green("[PASS]") : palette.red("[FAIL]");
|
|
178
255
|
}
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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-agentcash extensions",
|
|
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);
|
|
317
|
+
})
|
|
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(", ");
|
|
194
339
|
return [
|
|
195
340
|
entry.stage,
|
|
196
341
|
asYesNo(entry.valid),
|
|
197
342
|
String(entry.resourceCount),
|
|
198
343
|
`${entry.durationMs}ms`,
|
|
199
|
-
|
|
200
|
-
|
|
344
|
+
result.selectedStage === entry.stage ? "yes" : "-",
|
|
345
|
+
codes || "-"
|
|
201
346
|
];
|
|
202
347
|
});
|
|
348
|
+
return [header, ...rows];
|
|
203
349
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
350
|
+
function resourceMatrixRows(result) {
|
|
351
|
+
const sorted = [...result.resources].sort((a, b) => {
|
|
206
352
|
if (a.path !== b.path) return a.path.localeCompare(b.path);
|
|
207
353
|
if (a.method !== b.method) return a.method.localeCompare(b.method);
|
|
208
354
|
return a.origin.localeCompare(b.origin);
|
|
209
|
-
})
|
|
355
|
+
});
|
|
356
|
+
const header = ["method", "path", "auth", "pricing", "source", "verified", "notes"];
|
|
357
|
+
const rows = sorted.map((resource) => [
|
|
210
358
|
resource.method,
|
|
211
359
|
resource.path,
|
|
212
360
|
resource.authHint ?? "-",
|
|
@@ -215,61 +363,226 @@ function resourceRows(result) {
|
|
|
215
363
|
asYesNo(resource.verified),
|
|
216
364
|
resource.authHint === "siwx" ? "siwx" : "-"
|
|
217
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];
|
|
218
382
|
}
|
|
219
|
-
function
|
|
220
|
-
const ok = run.result.resources.length > 0;
|
|
221
|
-
const status = ok ? palette.green("PASS") : palette.red("FAIL");
|
|
222
|
-
const warningSummary = summarizeWarnings(run.result.warnings);
|
|
223
|
-
const mode = options.compatMode;
|
|
383
|
+
function harnessBudgetTable(report) {
|
|
224
384
|
return [
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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");
|
|
461
|
+
}
|
|
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");
|
|
238
514
|
}
|
|
239
515
|
function renderSummary(run, options) {
|
|
240
516
|
const palette = createPalette(options.color);
|
|
241
|
-
|
|
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");
|
|
242
533
|
}
|
|
243
534
|
function renderVerbose(run, options) {
|
|
244
535
|
const palette = createPalette(options.color);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
|
|
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));
|
|
255
557
|
if (run.result.resources.length === 0) {
|
|
256
|
-
|
|
558
|
+
body.push(palette.dim("(no discovered resources)"));
|
|
257
559
|
} else {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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));
|
|
264
577
|
}
|
|
265
578
|
if (run.result.upgradeReasons.length > 0) {
|
|
266
|
-
|
|
267
|
-
|
|
579
|
+
body.push("");
|
|
580
|
+
body.push(sectionHeader("Upgrade guidance", palette));
|
|
268
581
|
for (const reason of run.result.upgradeReasons) {
|
|
269
|
-
|
|
582
|
+
body.push(`- ${reason}`);
|
|
270
583
|
}
|
|
271
584
|
}
|
|
272
|
-
return
|
|
585
|
+
return body.join("\n");
|
|
273
586
|
}
|
|
274
587
|
function renderJson(run, options) {
|
|
275
588
|
return JSON.stringify(
|
|
@@ -289,13 +602,17 @@ function renderJson(run, options) {
|
|
|
289
602
|
probeEnabled: options.probe,
|
|
290
603
|
probeCandidateCount: run.probeCandidateCount,
|
|
291
604
|
timeoutMs: options.timeoutMs
|
|
292
|
-
}
|
|
605
|
+
},
|
|
606
|
+
...run.harness ? { harness: run.harness } : {}
|
|
293
607
|
},
|
|
294
608
|
null,
|
|
295
609
|
2
|
|
296
610
|
);
|
|
297
611
|
}
|
|
298
612
|
|
|
613
|
+
// src/mmm-enabled.ts
|
|
614
|
+
var isMmmEnabled = () => "0.1.2".includes("-mmm");
|
|
615
|
+
|
|
299
616
|
// src/core/constants.ts
|
|
300
617
|
var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
|
|
301
618
|
var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
|
|
@@ -1248,7 +1565,7 @@ function detectProtocols(response) {
|
|
|
1248
1565
|
}
|
|
1249
1566
|
const authHeader = response.headers.get("www-authenticate")?.toLowerCase() ?? "";
|
|
1250
1567
|
if (authHeader.includes("x402")) protocols.add("x402");
|
|
1251
|
-
if (authHeader.includes("mpp")) protocols.add("mpp");
|
|
1568
|
+
if (isMmmEnabled() && authHeader.includes("mpp")) protocols.add("mpp");
|
|
1252
1569
|
return [...protocols];
|
|
1253
1570
|
}
|
|
1254
1571
|
async function runProbeStage(options) {
|
|
@@ -1580,7 +1897,7 @@ async function runDiscovery({
|
|
|
1580
1897
|
includeRaw
|
|
1581
1898
|
})
|
|
1582
1899
|
);
|
|
1583
|
-
if (includeInteropMpp) {
|
|
1900
|
+
if (includeInteropMpp && isMmmEnabled()) {
|
|
1584
1901
|
stages.push(
|
|
1585
1902
|
() => runInteropMppStage({
|
|
1586
1903
|
origin,
|
|
@@ -1682,6 +1999,216 @@ async function runDiscovery({
|
|
|
1682
1999
|
};
|
|
1683
2000
|
}
|
|
1684
2001
|
|
|
2002
|
+
// src/harness.ts
|
|
2003
|
+
var INTENT_TRIGGERS = ["x402", "mpp", "pay for", "micropayment", "agentic commerce"];
|
|
2004
|
+
var CLIENT_PROFILES = {
|
|
2005
|
+
"claude-code": {
|
|
2006
|
+
id: "claude-code",
|
|
2007
|
+
label: "Claude Code MCP Harness",
|
|
2008
|
+
surface: "mcp",
|
|
2009
|
+
defaultContextWindowTokens: 2e5,
|
|
2010
|
+
zeroHopBudgetPercent: 0.1,
|
|
2011
|
+
notes: "Targets environments where MCP context can use roughly 10% of total context."
|
|
2012
|
+
},
|
|
2013
|
+
"skill-cli": {
|
|
2014
|
+
id: "skill-cli",
|
|
2015
|
+
label: "Skill + CLI Harness",
|
|
2016
|
+
surface: "skill-cli",
|
|
2017
|
+
defaultContextWindowTokens: 2e5,
|
|
2018
|
+
zeroHopBudgetPercent: 0.02,
|
|
2019
|
+
notes: "Targets constrained skill contexts with title/description-heavy routing."
|
|
2020
|
+
},
|
|
2021
|
+
"generic-mcp": {
|
|
2022
|
+
id: "generic-mcp",
|
|
2023
|
+
label: "Generic MCP Harness",
|
|
2024
|
+
surface: "mcp",
|
|
2025
|
+
defaultContextWindowTokens: 128e3,
|
|
2026
|
+
zeroHopBudgetPercent: 0.05,
|
|
2027
|
+
notes: "Conservative MCP profile when specific client budgets are unknown."
|
|
2028
|
+
},
|
|
2029
|
+
generic: {
|
|
2030
|
+
id: "generic",
|
|
2031
|
+
label: "Generic Agent Harness",
|
|
2032
|
+
surface: "generic",
|
|
2033
|
+
defaultContextWindowTokens: 128e3,
|
|
2034
|
+
zeroHopBudgetPercent: 0.03,
|
|
2035
|
+
notes: "Fallback profile for unknown clients."
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
function isFirstPartyDomain(hostname) {
|
|
2039
|
+
const normalized = hostname.toLowerCase();
|
|
2040
|
+
return normalized.endsWith(".dev") && normalized.startsWith("stable");
|
|
2041
|
+
}
|
|
2042
|
+
function safeHostname(origin) {
|
|
2043
|
+
try {
|
|
2044
|
+
return new URL(origin).hostname;
|
|
2045
|
+
} catch {
|
|
2046
|
+
return origin.replace(/^https?:\/\//, "").split("/")[0] ?? origin;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
function previewText(text) {
|
|
2050
|
+
if (!text) return null;
|
|
2051
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
2052
|
+
if (!compact) return null;
|
|
2053
|
+
return compact.length > 220 ? `${compact.slice(0, 217)}...` : compact;
|
|
2054
|
+
}
|
|
2055
|
+
function toAuthModeCount(resources) {
|
|
2056
|
+
const counts = {
|
|
2057
|
+
paid: 0,
|
|
2058
|
+
siwx: 0,
|
|
2059
|
+
apiKey: 0,
|
|
2060
|
+
unprotected: 0,
|
|
2061
|
+
unknown: 0
|
|
2062
|
+
};
|
|
2063
|
+
for (const resource of resources) {
|
|
2064
|
+
const mode = resource.authHint;
|
|
2065
|
+
if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
|
|
2066
|
+
counts[mode] += 1;
|
|
2067
|
+
} else {
|
|
2068
|
+
counts.unknown += 1;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return counts;
|
|
2072
|
+
}
|
|
2073
|
+
function toSourceCount(resources) {
|
|
2074
|
+
return resources.reduce(
|
|
2075
|
+
(acc, resource) => {
|
|
2076
|
+
acc[resource.source] = (acc[resource.source] ?? 0) + 1;
|
|
2077
|
+
return acc;
|
|
2078
|
+
},
|
|
2079
|
+
{}
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
function toL2Entries(resources) {
|
|
2083
|
+
return [...resources].sort((a, b) => {
|
|
2084
|
+
if (a.path !== b.path) return a.path.localeCompare(b.path);
|
|
2085
|
+
if (a.method !== b.method) return a.method.localeCompare(b.method);
|
|
2086
|
+
return a.resourceKey.localeCompare(b.resourceKey);
|
|
2087
|
+
}).map((resource) => ({
|
|
2088
|
+
resourceKey: resource.resourceKey,
|
|
2089
|
+
method: resource.method,
|
|
2090
|
+
path: resource.path,
|
|
2091
|
+
source: resource.source,
|
|
2092
|
+
authMode: resource.authHint ?? null
|
|
2093
|
+
}));
|
|
2094
|
+
}
|
|
2095
|
+
function getLlmsTxtInfo(result) {
|
|
2096
|
+
const llmsTxtUrl = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? null;
|
|
2097
|
+
const llmsTxt = result.rawSources?.llmsTxt;
|
|
2098
|
+
if (llmsTxt) {
|
|
2099
|
+
return {
|
|
2100
|
+
llmsTxtUrl,
|
|
2101
|
+
llmsTxtTokenEstimate: estimateTokenCount(llmsTxt),
|
|
2102
|
+
guidancePreview: previewText(llmsTxt),
|
|
2103
|
+
guidanceStatus: "present"
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
if (llmsTxtUrl) {
|
|
2107
|
+
const failed = result.warnings.some((warning2) => warning2.code === "LLMSTXT_FETCH_FAILED");
|
|
2108
|
+
return {
|
|
2109
|
+
llmsTxtUrl,
|
|
2110
|
+
llmsTxtTokenEstimate: 0,
|
|
2111
|
+
guidancePreview: null,
|
|
2112
|
+
guidanceStatus: failed ? "advertised_but_unfetched" : "missing"
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
return {
|
|
2116
|
+
llmsTxtUrl: null,
|
|
2117
|
+
llmsTxtTokenEstimate: 0,
|
|
2118
|
+
guidancePreview: null,
|
|
2119
|
+
guidanceStatus: "not_advertised"
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
function getHarnessClientProfile(client = "claude-code") {
|
|
2123
|
+
return CLIENT_PROFILES[client] ?? CLIENT_PROFILES.generic;
|
|
2124
|
+
}
|
|
2125
|
+
function buildContextHarnessReport(options) {
|
|
2126
|
+
const client = getHarnessClientProfile(options.client);
|
|
2127
|
+
const contextWindowTokens = options.contextWindowTokens ?? client.defaultContextWindowTokens;
|
|
2128
|
+
const zeroHopBudgetTokens = Math.floor(contextWindowTokens * client.zeroHopBudgetPercent);
|
|
2129
|
+
const hostname = safeHostname(options.result.origin);
|
|
2130
|
+
const domainClass = isFirstPartyDomain(hostname) ? "first-party" : "ugc";
|
|
2131
|
+
const l0Summary = "Route payment-capable intents to agentcash and use install/discover commands for progressive disclosure.";
|
|
2132
|
+
const l1Summary = "Expose installed domain routing hints and trigger fan-out into L2/L3 commands when user intent matches domain capabilities.";
|
|
2133
|
+
const l0EstimatedTokens = estimateTokenCount(
|
|
2134
|
+
`${INTENT_TRIGGERS.join(", ")} npx agentcash install npx agentcash install-ext`
|
|
2135
|
+
);
|
|
2136
|
+
const l1EstimatedTokens = estimateTokenCount(
|
|
2137
|
+
`${hostname} ${l1Summary} npx agentcash discover ${hostname} npx agentcash discover ${hostname} --verbose`
|
|
2138
|
+
);
|
|
2139
|
+
const llmsInfo = getLlmsTxtInfo(options.result);
|
|
2140
|
+
return {
|
|
2141
|
+
target: options.target,
|
|
2142
|
+
origin: options.result.origin,
|
|
2143
|
+
client,
|
|
2144
|
+
budget: {
|
|
2145
|
+
contextWindowTokens,
|
|
2146
|
+
zeroHopBudgetTokens,
|
|
2147
|
+
estimatedZeroHopTokens: l0EstimatedTokens + l1EstimatedTokens,
|
|
2148
|
+
withinBudget: l0EstimatedTokens + l1EstimatedTokens <= zeroHopBudgetTokens
|
|
2149
|
+
},
|
|
2150
|
+
levels: {
|
|
2151
|
+
l0: {
|
|
2152
|
+
layer: "L0",
|
|
2153
|
+
intentTriggers: INTENT_TRIGGERS,
|
|
2154
|
+
installCommand: "npx agentcash install",
|
|
2155
|
+
deliverySurfaces: ["MCP", "Skill+CLI"],
|
|
2156
|
+
summary: l0Summary,
|
|
2157
|
+
estimatedTokens: l0EstimatedTokens
|
|
2158
|
+
},
|
|
2159
|
+
l1: {
|
|
2160
|
+
layer: "L1",
|
|
2161
|
+
domain: hostname,
|
|
2162
|
+
domainClass,
|
|
2163
|
+
selectedDiscoveryStage: options.result.selectedStage ?? null,
|
|
2164
|
+
summary: l1Summary,
|
|
2165
|
+
fanoutCommands: [
|
|
2166
|
+
`npx agentcash discover ${hostname}`,
|
|
2167
|
+
`npx agentcash discover ${hostname} --verbose`
|
|
2168
|
+
],
|
|
2169
|
+
estimatedTokens: l1EstimatedTokens
|
|
2170
|
+
},
|
|
2171
|
+
l2: {
|
|
2172
|
+
layer: "L2",
|
|
2173
|
+
command: `npx agentcash discover ${hostname}`,
|
|
2174
|
+
tokenLight: true,
|
|
2175
|
+
resourceCount: options.result.resources.length,
|
|
2176
|
+
resources: toL2Entries(options.result.resources)
|
|
2177
|
+
},
|
|
2178
|
+
l3: {
|
|
2179
|
+
layer: "L3",
|
|
2180
|
+
command: `npx agentcash discover ${hostname} --verbose`,
|
|
2181
|
+
detailLevel: "endpoint-schema-and-metadata",
|
|
2182
|
+
countsByAuthMode: toAuthModeCount(options.result.resources),
|
|
2183
|
+
countsBySource: toSourceCount(options.result.resources),
|
|
2184
|
+
pricedResourceCount: options.result.resources.filter(
|
|
2185
|
+
(resource) => resource.authHint === "paid"
|
|
2186
|
+
).length
|
|
2187
|
+
},
|
|
2188
|
+
l4: {
|
|
2189
|
+
layer: "L4",
|
|
2190
|
+
guidanceSource: llmsInfo.llmsTxtUrl ? "llms.txt" : "none",
|
|
2191
|
+
llmsTxtUrl: llmsInfo.llmsTxtUrl,
|
|
2192
|
+
llmsTxtTokenEstimate: llmsInfo.llmsTxtTokenEstimate,
|
|
2193
|
+
guidancePreview: llmsInfo.guidancePreview,
|
|
2194
|
+
guidanceStatus: llmsInfo.guidanceStatus
|
|
2195
|
+
},
|
|
2196
|
+
l5: {
|
|
2197
|
+
layer: "L5",
|
|
2198
|
+
status: "out_of_scope",
|
|
2199
|
+
note: "Cross-domain composition is intentionally out of scope for discovery v1."
|
|
2200
|
+
}
|
|
2201
|
+
},
|
|
2202
|
+
diagnostics: {
|
|
2203
|
+
warningCount: options.result.warnings.length,
|
|
2204
|
+
errorWarningCount: options.result.warnings.filter((warning2) => warning2.severity === "error").length,
|
|
2205
|
+
selectedStage: options.result.selectedStage ?? null,
|
|
2206
|
+
upgradeSuggested: options.result.upgradeSuggested,
|
|
2207
|
+
upgradeReasons: options.result.upgradeReasons
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
|
|
1685
2212
|
// src/index.ts
|
|
1686
2213
|
async function discoverDetailed(options) {
|
|
1687
2214
|
return await runDiscovery({ detailed: true, options });
|
|
@@ -1732,7 +2259,7 @@ async function runAudit(options, deps = {}) {
|
|
|
1732
2259
|
headers: {
|
|
1733
2260
|
"user-agent": CLI_USER_AGENT
|
|
1734
2261
|
},
|
|
1735
|
-
rawView: "none"
|
|
2262
|
+
rawView: options.harness ? "full" : "none"
|
|
1736
2263
|
};
|
|
1737
2264
|
const startedAt = Date.now();
|
|
1738
2265
|
let result = await discoverDetailedFn(baseOptions);
|
|
@@ -1750,7 +2277,15 @@ async function runAudit(options, deps = {}) {
|
|
|
1750
2277
|
return {
|
|
1751
2278
|
result,
|
|
1752
2279
|
durationMs: Date.now() - startedAt,
|
|
1753
|
-
probeCandidateCount
|
|
2280
|
+
probeCandidateCount,
|
|
2281
|
+
...options.harness ? {
|
|
2282
|
+
harness: buildContextHarnessReport({
|
|
2283
|
+
target: options.target,
|
|
2284
|
+
result,
|
|
2285
|
+
client: options.client,
|
|
2286
|
+
contextWindowTokens: options.contextWindowTokens
|
|
2287
|
+
})
|
|
2288
|
+
} : {}
|
|
1754
2289
|
};
|
|
1755
2290
|
}
|
|
1756
2291
|
|