@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.cjs
CHANGED
|
@@ -51,6 +51,19 @@ function parseTimeoutMs(value) {
|
|
|
51
51
|
}
|
|
52
52
|
return parsed;
|
|
53
53
|
}
|
|
54
|
+
function parseClient(value) {
|
|
55
|
+
if (value === "claude-code" || value === "skill-cli" || value === "generic-mcp" || value === "generic") {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function parseContextWindowTokens(value) {
|
|
61
|
+
const parsed = Number(value);
|
|
62
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
54
67
|
function parseCliArgs(argv) {
|
|
55
68
|
let target;
|
|
56
69
|
let verbose = false;
|
|
@@ -59,6 +72,10 @@ function parseCliArgs(argv) {
|
|
|
59
72
|
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
60
73
|
let compatMode = DEFAULT_COMPAT_MODE;
|
|
61
74
|
let color = true;
|
|
75
|
+
let noTruncate = false;
|
|
76
|
+
let harness = false;
|
|
77
|
+
let client = "claude-code";
|
|
78
|
+
let contextWindowTokens;
|
|
62
79
|
for (let i = 0; i < argv.length; i += 1) {
|
|
63
80
|
const arg = argv[i];
|
|
64
81
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -80,6 +97,14 @@ function parseCliArgs(argv) {
|
|
|
80
97
|
color = false;
|
|
81
98
|
continue;
|
|
82
99
|
}
|
|
100
|
+
if (arg === "--no-truncate") {
|
|
101
|
+
noTruncate = true;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (arg === "--harness") {
|
|
105
|
+
harness = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
83
108
|
if (arg === "--compat") {
|
|
84
109
|
const value = argv[i + 1];
|
|
85
110
|
if (!value) {
|
|
@@ -112,6 +137,38 @@ function parseCliArgs(argv) {
|
|
|
112
137
|
i += 1;
|
|
113
138
|
continue;
|
|
114
139
|
}
|
|
140
|
+
if (arg === "--client") {
|
|
141
|
+
const value = argv[i + 1];
|
|
142
|
+
if (!value) {
|
|
143
|
+
return { kind: "error", message: "Missing value for --client." };
|
|
144
|
+
}
|
|
145
|
+
const parsed = parseClient(value);
|
|
146
|
+
if (!parsed) {
|
|
147
|
+
return {
|
|
148
|
+
kind: "error",
|
|
149
|
+
message: "Invalid --client value '" + value + "'. Expected: claude-code | skill-cli | generic-mcp | generic."
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
client = parsed;
|
|
153
|
+
i += 1;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (arg === "--context-window-tokens") {
|
|
157
|
+
const value = argv[i + 1];
|
|
158
|
+
if (!value) {
|
|
159
|
+
return { kind: "error", message: "Missing value for --context-window-tokens." };
|
|
160
|
+
}
|
|
161
|
+
const parsed = parseContextWindowTokens(value);
|
|
162
|
+
if (!parsed) {
|
|
163
|
+
return {
|
|
164
|
+
kind: "error",
|
|
165
|
+
message: `Invalid --context-window-tokens value '${value}'. Expected a positive integer.`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
contextWindowTokens = parsed;
|
|
169
|
+
i += 1;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
115
172
|
if (arg.startsWith("-")) {
|
|
116
173
|
return { kind: "error", message: `Unknown flag '${arg}'.` };
|
|
117
174
|
}
|
|
@@ -135,7 +192,11 @@ function parseCliArgs(argv) {
|
|
|
135
192
|
compatMode,
|
|
136
193
|
probe,
|
|
137
194
|
timeoutMs,
|
|
138
|
-
color
|
|
195
|
+
color,
|
|
196
|
+
noTruncate,
|
|
197
|
+
harness,
|
|
198
|
+
client,
|
|
199
|
+
...contextWindowTokens ? { contextWindowTokens } : {}
|
|
139
200
|
}
|
|
140
201
|
};
|
|
141
202
|
}
|
|
@@ -148,13 +209,19 @@ function renderHelp() {
|
|
|
148
209
|
" --json Emit JSON output",
|
|
149
210
|
" --compat <mode> Compatibility mode: on | off | strict (default: on)",
|
|
150
211
|
" --probe Re-run with probe candidates from discovered paths",
|
|
212
|
+
" --harness Render L0-L5 context harness audit view",
|
|
213
|
+
" --client <id> Harness client: claude-code | skill-cli | generic-mcp | generic",
|
|
214
|
+
" --context-window-tokens <n>",
|
|
215
|
+
" Override client context window for harness budget checks",
|
|
151
216
|
" --timeout-ms <ms> Per-request timeout in milliseconds (default: 5000)",
|
|
152
217
|
" --no-color Disable ANSI colors",
|
|
218
|
+
" --no-truncate Disable output truncation in verbose tables",
|
|
153
219
|
" -h, --help Show help"
|
|
154
220
|
].join("\n");
|
|
155
221
|
}
|
|
156
222
|
|
|
157
223
|
// src/cli/render.ts
|
|
224
|
+
var import_table = require("table");
|
|
158
225
|
function createPalette(enabled) {
|
|
159
226
|
const wrap = (code) => (value) => enabled ? `\x1B[${code}m${value}\x1B[0m` : value;
|
|
160
227
|
return {
|
|
@@ -177,7 +244,7 @@ function summarizeWarnings(warnings) {
|
|
|
177
244
|
},
|
|
178
245
|
{}
|
|
179
246
|
);
|
|
180
|
-
const codes = Object.entries(codeCounts).sort((a, b) => b[1] - a[1]).
|
|
247
|
+
const codes = Object.entries(codeCounts).sort((a, b) => b[1] - a[1]).map(([code, count]) => ({ code, count }));
|
|
181
248
|
return {
|
|
182
249
|
total: warnings.length,
|
|
183
250
|
errors,
|
|
@@ -186,53 +253,134 @@ function summarizeWarnings(warnings) {
|
|
|
186
253
|
codes
|
|
187
254
|
};
|
|
188
255
|
}
|
|
256
|
+
function asYesNo(value) {
|
|
257
|
+
return value ? "yes" : "no";
|
|
258
|
+
}
|
|
259
|
+
function truncate(value, max, noTruncate) {
|
|
260
|
+
if (noTruncate || value.length <= max) {
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
if (max <= 1) return value.slice(0, max);
|
|
264
|
+
return `${value.slice(0, Math.max(1, max - 1))}...`;
|
|
265
|
+
}
|
|
189
266
|
function formatPricing(resource) {
|
|
190
267
|
if (resource.authHint !== "paid") return "-";
|
|
191
268
|
if (resource.pricing) {
|
|
192
269
|
if (resource.pricing.pricingMode === "fixed") {
|
|
193
|
-
return `fixed
|
|
270
|
+
return resource.pricing.price ? `fixed ${resource.pricing.price}` : "fixed";
|
|
194
271
|
}
|
|
195
272
|
if (resource.pricing.pricingMode === "range") {
|
|
196
|
-
return `range
|
|
273
|
+
return `range ${resource.pricing.minPrice ?? "?"}-${resource.pricing.maxPrice ?? "?"}`;
|
|
197
274
|
}
|
|
198
275
|
return "quote";
|
|
199
276
|
}
|
|
200
|
-
return resource.priceHint
|
|
277
|
+
return resource.priceHint ? `hint ${resource.priceHint}` : "-";
|
|
201
278
|
}
|
|
202
|
-
function
|
|
203
|
-
return
|
|
279
|
+
function statusBadge(ok, palette) {
|
|
280
|
+
return ok ? palette.green("[PASS]") : palette.red("[FAIL]");
|
|
204
281
|
}
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
282
|
+
function riskBadge(summary, palette) {
|
|
283
|
+
if (summary.errors > 0) return palette.red("HIGH");
|
|
284
|
+
if (summary.warns > 0) return palette.yellow("MEDIUM");
|
|
285
|
+
return palette.green("LOW");
|
|
286
|
+
}
|
|
287
|
+
function sectionHeader(title, palette) {
|
|
288
|
+
return `${palette.bold(title)}
|
|
289
|
+
${palette.dim("-".repeat(title.length))}`;
|
|
290
|
+
}
|
|
291
|
+
function introBlock(run, options, palette) {
|
|
292
|
+
const summary = summarizeWarnings(run.result.warnings);
|
|
293
|
+
const topCodes = summary.codes.slice(0, 3).map(({ code, count }) => `${code} (${count})`).join(", ");
|
|
294
|
+
const lines = [
|
|
295
|
+
`${palette.bold("AGENTCASH DISCOVERY AUDIT")} ${statusBadge(run.result.resources.length > 0, palette)}`,
|
|
296
|
+
`${palette.dim("=".repeat(74))}`,
|
|
297
|
+
`Target ${options.target}`,
|
|
298
|
+
`Origin ${run.result.origin}`,
|
|
299
|
+
`Stage ${run.result.selectedStage ?? "none"} Compat: ${run.result.compatMode}`,
|
|
300
|
+
`Resources ${run.result.resources.length} Duration: ${run.durationMs}ms`,
|
|
301
|
+
`Risk ${riskBadge(summary, palette)} Upgrade suggested: ${run.result.upgradeSuggested ? "yes" : "no"}`,
|
|
302
|
+
`Warnings error=${summary.errors} warn=${summary.warns} info=${summary.infos}`,
|
|
303
|
+
`Top signals ${topCodes || "-"}`
|
|
304
|
+
];
|
|
305
|
+
return lines.join("\n");
|
|
306
|
+
}
|
|
307
|
+
function outcomeLine(run) {
|
|
308
|
+
const selectedStage = run.result.selectedStage ?? "none";
|
|
309
|
+
if (run.result.resources.length === 0) {
|
|
310
|
+
return "No usable discovery metadata was found.";
|
|
311
|
+
}
|
|
312
|
+
if (selectedStage === "openapi" || selectedStage === "override") {
|
|
313
|
+
return `Canonical discovery succeeded via ${selectedStage}.`;
|
|
314
|
+
}
|
|
315
|
+
return `Discovery succeeded, but only via legacy stage ${selectedStage}.`;
|
|
316
|
+
}
|
|
317
|
+
function actionItems(run, options) {
|
|
318
|
+
const target = options.target;
|
|
319
|
+
if (run.result.resources.length === 0) {
|
|
320
|
+
return [
|
|
321
|
+
`Run deeper diagnostics: npx @agentcash/discovery ${target} -v --probe --compat on`,
|
|
322
|
+
`Check that ${target} serves /openapi.json or /.well-known/x402`,
|
|
323
|
+
"If endpoint exists, verify it returns parseable JSON and non-empty resources"
|
|
324
|
+
];
|
|
325
|
+
}
|
|
326
|
+
if (run.result.selectedStage === "openapi" || run.result.selectedStage === "override") {
|
|
327
|
+
return [
|
|
328
|
+
`Run strict mode hardening: npx @agentcash/discovery ${target} --compat strict -v`,
|
|
329
|
+
"Reduce warning count to near-zero and keep pricing/auth hints explicit"
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
return [
|
|
333
|
+
"Publish canonical OpenAPI discovery metadata with x-agentcash extensions",
|
|
334
|
+
`Validate migration path: npx @agentcash/discovery ${target} --compat strict -v --probe`,
|
|
335
|
+
"Keep legacy well-known path only during sunset window"
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
function buildTable(data, maxColWidths, noTruncate) {
|
|
339
|
+
const rendered = data.map(
|
|
340
|
+
(row) => row.map((cell, idx) => {
|
|
341
|
+
const width = maxColWidths[idx] ?? 120;
|
|
342
|
+
return noTruncate ? cell : truncate(cell, width, false);
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
return (0, import_table.table)(rendered, {
|
|
346
|
+
border: (0, import_table.getBorderCharacters)("ramac"),
|
|
347
|
+
columns: maxColWidths.reduce(
|
|
348
|
+
(acc, width, idx) => {
|
|
349
|
+
acc[idx] = noTruncate ? { wrapWord: false, alignment: "left" } : { width, truncate: width, wrapWord: false, alignment: "left" };
|
|
350
|
+
return acc;
|
|
351
|
+
},
|
|
352
|
+
{}
|
|
353
|
+
),
|
|
354
|
+
drawHorizontalLine: (index, size) => index === 0 || index === 1 || index === size,
|
|
355
|
+
columnDefault: {
|
|
356
|
+
paddingLeft: 1,
|
|
357
|
+
paddingRight: 1
|
|
358
|
+
}
|
|
359
|
+
}).trimEnd();
|
|
360
|
+
}
|
|
361
|
+
function stageMatrixRows(result) {
|
|
362
|
+
const header = ["stage", "valid", "resources", "duration", "selected", "warning codes"];
|
|
363
|
+
const rows = result.trace.map((entry) => {
|
|
364
|
+
const codes = [...new Set(entry.warnings.map((warning2) => warning2.code))].join(", ");
|
|
220
365
|
return [
|
|
221
366
|
entry.stage,
|
|
222
367
|
asYesNo(entry.valid),
|
|
223
368
|
String(entry.resourceCount),
|
|
224
369
|
`${entry.durationMs}ms`,
|
|
225
|
-
|
|
226
|
-
|
|
370
|
+
result.selectedStage === entry.stage ? "yes" : "-",
|
|
371
|
+
codes || "-"
|
|
227
372
|
];
|
|
228
373
|
});
|
|
374
|
+
return [header, ...rows];
|
|
229
375
|
}
|
|
230
|
-
function
|
|
231
|
-
|
|
376
|
+
function resourceMatrixRows(result) {
|
|
377
|
+
const sorted = [...result.resources].sort((a, b) => {
|
|
232
378
|
if (a.path !== b.path) return a.path.localeCompare(b.path);
|
|
233
379
|
if (a.method !== b.method) return a.method.localeCompare(b.method);
|
|
234
380
|
return a.origin.localeCompare(b.origin);
|
|
235
|
-
})
|
|
381
|
+
});
|
|
382
|
+
const header = ["method", "path", "auth", "pricing", "source", "verified", "notes"];
|
|
383
|
+
const rows = sorted.map((resource) => [
|
|
236
384
|
resource.method,
|
|
237
385
|
resource.path,
|
|
238
386
|
resource.authHint ?? "-",
|
|
@@ -241,61 +389,226 @@ function resourceRows(result) {
|
|
|
241
389
|
asYesNo(resource.verified),
|
|
242
390
|
resource.authHint === "siwx" ? "siwx" : "-"
|
|
243
391
|
]);
|
|
392
|
+
return [header, ...rows];
|
|
393
|
+
}
|
|
394
|
+
function warningRows(summary, result) {
|
|
395
|
+
const hintByCode = /* @__PURE__ */ new Map();
|
|
396
|
+
for (const warning2 of result.warnings) {
|
|
397
|
+
if (!hintByCode.has(warning2.code)) {
|
|
398
|
+
hintByCode.set(warning2.code, warning2.hint ?? warning2.message);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const header = ["severity", "code", "count", "hint/message"];
|
|
402
|
+
const rows = summary.codes.slice(0, 8).map(({ code, count }) => {
|
|
403
|
+
const sample = hintByCode.get(code) ?? "-";
|
|
404
|
+
const severity = code.includes("NO_DISCOVERY") ? "error" : code.includes("LEGACY") ? "warn" : "info";
|
|
405
|
+
return [severity, code, String(count), sample];
|
|
406
|
+
});
|
|
407
|
+
return [header, ...rows];
|
|
244
408
|
}
|
|
245
|
-
function
|
|
246
|
-
const ok = run.result.resources.length > 0;
|
|
247
|
-
const status = ok ? palette.green("PASS") : palette.red("FAIL");
|
|
248
|
-
const warningSummary = summarizeWarnings(run.result.warnings);
|
|
249
|
-
const mode = options.compatMode;
|
|
409
|
+
function harnessBudgetTable(report) {
|
|
250
410
|
return [
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
411
|
+
["metric", "value"],
|
|
412
|
+
["client", `${report.client.id} (${report.client.surface})`],
|
|
413
|
+
["context window tokens", String(report.budget.contextWindowTokens)],
|
|
414
|
+
["zero-hop budget tokens", String(report.budget.zeroHopBudgetTokens)],
|
|
415
|
+
["estimated L0+L1 tokens", String(report.budget.estimatedZeroHopTokens)],
|
|
416
|
+
["within budget", report.budget.withinBudget ? "yes" : "no"]
|
|
417
|
+
];
|
|
418
|
+
}
|
|
419
|
+
function harnessLayerSummaryRows(report) {
|
|
420
|
+
return [
|
|
421
|
+
["layer", "what", "primary command", "status"],
|
|
422
|
+
[
|
|
423
|
+
"L0",
|
|
424
|
+
"trigger layer",
|
|
425
|
+
report.levels.l0.installCommand,
|
|
426
|
+
`${report.levels.l0.intentTriggers.length} triggers`
|
|
427
|
+
],
|
|
428
|
+
[
|
|
429
|
+
"L1",
|
|
430
|
+
"installed domain index",
|
|
431
|
+
report.levels.l1.fanoutCommands[0] ?? "-",
|
|
432
|
+
`${report.levels.l1.domainClass}; stage=${report.levels.l1.selectedDiscoveryStage ?? "none"}`
|
|
433
|
+
],
|
|
434
|
+
[
|
|
435
|
+
"L2",
|
|
436
|
+
"domain resources",
|
|
437
|
+
report.levels.l2.command,
|
|
438
|
+
`${report.levels.l2.resourceCount} resources`
|
|
439
|
+
],
|
|
440
|
+
[
|
|
441
|
+
"L3",
|
|
442
|
+
"resource details",
|
|
443
|
+
report.levels.l3.command,
|
|
444
|
+
`${report.levels.l3.pricedResourceCount} priced`
|
|
445
|
+
],
|
|
446
|
+
["L4", "domain guidance", report.levels.l4.llmsTxtUrl ?? "-", report.levels.l4.guidanceStatus],
|
|
447
|
+
["L5", "cross-domain composition", "-", report.levels.l5.status]
|
|
448
|
+
];
|
|
449
|
+
}
|
|
450
|
+
function harnessL2PreviewRows(report, noTruncate) {
|
|
451
|
+
const header = ["method", "path", "auth", "source"];
|
|
452
|
+
const rows = report.levels.l2.resources.slice(0, 15).map((resource) => [
|
|
453
|
+
resource.method,
|
|
454
|
+
truncate(resource.path, 54, noTruncate),
|
|
455
|
+
resource.authMode ?? "-",
|
|
456
|
+
resource.source
|
|
457
|
+
]);
|
|
458
|
+
return [header, ...rows];
|
|
459
|
+
}
|
|
460
|
+
function renderHarnessSummary(report, options, palette) {
|
|
461
|
+
const lines = [];
|
|
462
|
+
lines.push(
|
|
463
|
+
`${palette.bold("L0-L5 Context Harness")} ${statusBadge(report.levels.l2.resourceCount > 0, palette)}`
|
|
464
|
+
);
|
|
465
|
+
lines.push(`${palette.dim("=".repeat(74))}`);
|
|
466
|
+
lines.push(`Client ${report.client.label}`);
|
|
467
|
+
lines.push(`Domain ${report.levels.l1.domain}`);
|
|
468
|
+
lines.push(`L2 resources ${report.levels.l2.resourceCount}`);
|
|
469
|
+
lines.push(
|
|
470
|
+
`Budget ${report.budget.estimatedZeroHopTokens}/${report.budget.zeroHopBudgetTokens} tokens (L0+L1)`
|
|
471
|
+
);
|
|
472
|
+
lines.push("");
|
|
473
|
+
lines.push("Layer map:");
|
|
474
|
+
lines.push("1. L0 trigger surface -> install and route to agentcash");
|
|
475
|
+
lines.push(`2. L1 domain index -> ${report.levels.l1.fanoutCommands[0]}`);
|
|
476
|
+
lines.push(`3. L2 resources -> ${report.levels.l2.command}`);
|
|
477
|
+
lines.push(`4. L3 details -> ${report.levels.l3.command}`);
|
|
478
|
+
lines.push(
|
|
479
|
+
`5. L4 guidance -> ${report.levels.l4.guidanceStatus}${report.levels.l4.llmsTxtUrl ? ` (${report.levels.l4.llmsTxtUrl})` : ""}`
|
|
480
|
+
);
|
|
481
|
+
lines.push("6. L5 cross-domain -> out of scope in v1");
|
|
482
|
+
lines.push("");
|
|
483
|
+
lines.push(
|
|
484
|
+
`Next: npx @agentcash/discovery ${options.target} --harness -v --client ${report.client.id}`
|
|
485
|
+
);
|
|
486
|
+
return lines.join("\n");
|
|
487
|
+
}
|
|
488
|
+
function renderHarnessVerbose(report, options, palette) {
|
|
489
|
+
const lines = [];
|
|
490
|
+
lines.push(
|
|
491
|
+
`${palette.bold("L0-L5 Context Harness (Verbose)")} ${statusBadge(report.levels.l2.resourceCount > 0, palette)}`
|
|
492
|
+
);
|
|
493
|
+
lines.push(`${palette.dim("=".repeat(74))}`);
|
|
494
|
+
lines.push(`Target ${report.target}`);
|
|
495
|
+
lines.push(`Origin ${report.origin}`);
|
|
496
|
+
lines.push(`Client ${report.client.label}`);
|
|
497
|
+
lines.push("");
|
|
498
|
+
lines.push(sectionHeader("Budget check", palette));
|
|
499
|
+
lines.push(buildTable(harnessBudgetTable(report), [30, 42], options.noTruncate));
|
|
500
|
+
lines.push("");
|
|
501
|
+
lines.push(sectionHeader("Layer summary", palette));
|
|
502
|
+
lines.push(buildTable(harnessLayerSummaryRows(report), [8, 24, 44, 28], options.noTruncate));
|
|
503
|
+
lines.push("");
|
|
504
|
+
lines.push(sectionHeader("L2 resource preview", palette));
|
|
505
|
+
if (report.levels.l2.resources.length === 0) {
|
|
506
|
+
lines.push(palette.dim("(no discovered resources)"));
|
|
507
|
+
} else {
|
|
508
|
+
lines.push(
|
|
509
|
+
buildTable(
|
|
510
|
+
harnessL2PreviewRows(report, options.noTruncate),
|
|
511
|
+
[10, 54, 12, 20],
|
|
512
|
+
options.noTruncate
|
|
513
|
+
)
|
|
514
|
+
);
|
|
515
|
+
if (report.levels.l2.resources.length > 15) {
|
|
516
|
+
lines.push(
|
|
517
|
+
palette.dim(
|
|
518
|
+
`Showing 15/${report.levels.l2.resources.length} resources. Use --json to inspect full L2/L3 payload.`
|
|
519
|
+
)
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
lines.push("");
|
|
524
|
+
lines.push(sectionHeader("L4 guidance", palette));
|
|
525
|
+
lines.push(`status: ${report.levels.l4.guidanceStatus}`);
|
|
526
|
+
lines.push(`llms.txt url: ${report.levels.l4.llmsTxtUrl ?? "-"}`);
|
|
527
|
+
lines.push(`llms.txt tokens: ${report.levels.l4.llmsTxtTokenEstimate}`);
|
|
528
|
+
lines.push(`preview: ${report.levels.l4.guidancePreview ?? "-"}`);
|
|
529
|
+
lines.push("");
|
|
530
|
+
lines.push(sectionHeader("Diagnostics", palette));
|
|
531
|
+
lines.push(
|
|
532
|
+
`warnings: ${report.diagnostics.warningCount} (errors=${report.diagnostics.errorWarningCount})`
|
|
533
|
+
);
|
|
534
|
+
lines.push(`selected stage: ${report.diagnostics.selectedStage ?? "none"}`);
|
|
535
|
+
lines.push(`upgrade suggested: ${report.diagnostics.upgradeSuggested ? "yes" : "no"}`);
|
|
536
|
+
lines.push(
|
|
537
|
+
`upgrade reasons: ${report.diagnostics.upgradeReasons.length > 0 ? report.diagnostics.upgradeReasons.join(", ") : "-"}`
|
|
538
|
+
);
|
|
539
|
+
return lines.join("\n");
|
|
264
540
|
}
|
|
265
541
|
function renderSummary(run, options) {
|
|
266
542
|
const palette = createPalette(options.color);
|
|
267
|
-
|
|
543
|
+
if (options.harness && run.harness) {
|
|
544
|
+
return renderHarnessSummary(run.harness, options, palette);
|
|
545
|
+
}
|
|
546
|
+
const body = [];
|
|
547
|
+
body.push(introBlock(run, options, palette));
|
|
548
|
+
body.push("");
|
|
549
|
+
body.push(`Outcome: ${outcomeLine(run)}`);
|
|
550
|
+
const items = actionItems(run, options);
|
|
551
|
+
body.push("Next:");
|
|
552
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
553
|
+
body.push(`${i + 1}. ${items[i]}`);
|
|
554
|
+
}
|
|
555
|
+
if (options.probe) {
|
|
556
|
+
body.push(`Probe candidates considered: ${run.probeCandidateCount}`);
|
|
557
|
+
}
|
|
558
|
+
return body.join("\n");
|
|
268
559
|
}
|
|
269
560
|
function renderVerbose(run, options) {
|
|
270
561
|
const palette = createPalette(options.color);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
|
|
562
|
+
if (options.harness && run.harness) {
|
|
563
|
+
return renderHarnessVerbose(run.harness, options, palette);
|
|
564
|
+
}
|
|
565
|
+
const summary = summarizeWarnings(run.result.warnings);
|
|
566
|
+
const noTruncate = options.noTruncate;
|
|
567
|
+
const body = [];
|
|
568
|
+
body.push(introBlock(run, options, palette));
|
|
569
|
+
body.push("");
|
|
570
|
+
body.push(sectionHeader("What this means", palette));
|
|
571
|
+
body.push(outcomeLine(run));
|
|
572
|
+
body.push("");
|
|
573
|
+
body.push(sectionHeader("Recommended actions", palette));
|
|
574
|
+
const items = actionItems(run, options);
|
|
575
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
576
|
+
body.push(`${i + 1}. ${items[i]}`);
|
|
577
|
+
}
|
|
578
|
+
body.push("");
|
|
579
|
+
body.push(sectionHeader("Compatibility matrix", palette));
|
|
580
|
+
body.push(buildTable(stageMatrixRows(run.result), [18, 8, 10, 10, 10, 56], noTruncate));
|
|
581
|
+
body.push("");
|
|
582
|
+
body.push(sectionHeader("Resource matrix", palette));
|
|
281
583
|
if (run.result.resources.length === 0) {
|
|
282
|
-
|
|
584
|
+
body.push(palette.dim("(no discovered resources)"));
|
|
283
585
|
} else {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
586
|
+
const allRows = resourceMatrixRows(run.result);
|
|
587
|
+
const previewRows = allRows.slice(0, 16);
|
|
588
|
+
body.push(buildTable(previewRows, [8, 44, 10, 24, 18, 10, 10], noTruncate));
|
|
589
|
+
if (allRows.length > previewRows.length) {
|
|
590
|
+
body.push(
|
|
591
|
+
palette.dim(
|
|
592
|
+
`Showing ${previewRows.length - 1}/${allRows.length - 1} resources. Use --no-truncate or --json for complete output.`
|
|
593
|
+
)
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
body.push("");
|
|
598
|
+
body.push(sectionHeader("Warning breakdown", palette));
|
|
599
|
+
if (summary.codes.length === 0) {
|
|
600
|
+
body.push(palette.dim("(no warnings)"));
|
|
601
|
+
} else {
|
|
602
|
+
body.push(buildTable(warningRows(summary, run.result), [10, 34, 8, 62], noTruncate));
|
|
290
603
|
}
|
|
291
604
|
if (run.result.upgradeReasons.length > 0) {
|
|
292
|
-
|
|
293
|
-
|
|
605
|
+
body.push("");
|
|
606
|
+
body.push(sectionHeader("Upgrade guidance", palette));
|
|
294
607
|
for (const reason of run.result.upgradeReasons) {
|
|
295
|
-
|
|
608
|
+
body.push(`- ${reason}`);
|
|
296
609
|
}
|
|
297
610
|
}
|
|
298
|
-
return
|
|
611
|
+
return body.join("\n");
|
|
299
612
|
}
|
|
300
613
|
function renderJson(run, options) {
|
|
301
614
|
return JSON.stringify(
|
|
@@ -315,13 +628,17 @@ function renderJson(run, options) {
|
|
|
315
628
|
probeEnabled: options.probe,
|
|
316
629
|
probeCandidateCount: run.probeCandidateCount,
|
|
317
630
|
timeoutMs: options.timeoutMs
|
|
318
|
-
}
|
|
631
|
+
},
|
|
632
|
+
...run.harness ? { harness: run.harness } : {}
|
|
319
633
|
},
|
|
320
634
|
null,
|
|
321
635
|
2
|
|
322
636
|
);
|
|
323
637
|
}
|
|
324
638
|
|
|
639
|
+
// src/mmm-enabled.ts
|
|
640
|
+
var isMmmEnabled = () => "0.1.2".includes("-mmm");
|
|
641
|
+
|
|
325
642
|
// src/core/constants.ts
|
|
326
643
|
var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
|
|
327
644
|
var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
|
|
@@ -1274,7 +1591,7 @@ function detectProtocols(response) {
|
|
|
1274
1591
|
}
|
|
1275
1592
|
const authHeader = response.headers.get("www-authenticate")?.toLowerCase() ?? "";
|
|
1276
1593
|
if (authHeader.includes("x402")) protocols.add("x402");
|
|
1277
|
-
if (authHeader.includes("mpp")) protocols.add("mpp");
|
|
1594
|
+
if (isMmmEnabled() && authHeader.includes("mpp")) protocols.add("mpp");
|
|
1278
1595
|
return [...protocols];
|
|
1279
1596
|
}
|
|
1280
1597
|
async function runProbeStage(options) {
|
|
@@ -1606,7 +1923,7 @@ async function runDiscovery({
|
|
|
1606
1923
|
includeRaw
|
|
1607
1924
|
})
|
|
1608
1925
|
);
|
|
1609
|
-
if (includeInteropMpp) {
|
|
1926
|
+
if (includeInteropMpp && isMmmEnabled()) {
|
|
1610
1927
|
stages.push(
|
|
1611
1928
|
() => runInteropMppStage({
|
|
1612
1929
|
origin,
|
|
@@ -1708,6 +2025,216 @@ async function runDiscovery({
|
|
|
1708
2025
|
};
|
|
1709
2026
|
}
|
|
1710
2027
|
|
|
2028
|
+
// src/harness.ts
|
|
2029
|
+
var INTENT_TRIGGERS = ["x402", "mpp", "pay for", "micropayment", "agentic commerce"];
|
|
2030
|
+
var CLIENT_PROFILES = {
|
|
2031
|
+
"claude-code": {
|
|
2032
|
+
id: "claude-code",
|
|
2033
|
+
label: "Claude Code MCP Harness",
|
|
2034
|
+
surface: "mcp",
|
|
2035
|
+
defaultContextWindowTokens: 2e5,
|
|
2036
|
+
zeroHopBudgetPercent: 0.1,
|
|
2037
|
+
notes: "Targets environments where MCP context can use roughly 10% of total context."
|
|
2038
|
+
},
|
|
2039
|
+
"skill-cli": {
|
|
2040
|
+
id: "skill-cli",
|
|
2041
|
+
label: "Skill + CLI Harness",
|
|
2042
|
+
surface: "skill-cli",
|
|
2043
|
+
defaultContextWindowTokens: 2e5,
|
|
2044
|
+
zeroHopBudgetPercent: 0.02,
|
|
2045
|
+
notes: "Targets constrained skill contexts with title/description-heavy routing."
|
|
2046
|
+
},
|
|
2047
|
+
"generic-mcp": {
|
|
2048
|
+
id: "generic-mcp",
|
|
2049
|
+
label: "Generic MCP Harness",
|
|
2050
|
+
surface: "mcp",
|
|
2051
|
+
defaultContextWindowTokens: 128e3,
|
|
2052
|
+
zeroHopBudgetPercent: 0.05,
|
|
2053
|
+
notes: "Conservative MCP profile when specific client budgets are unknown."
|
|
2054
|
+
},
|
|
2055
|
+
generic: {
|
|
2056
|
+
id: "generic",
|
|
2057
|
+
label: "Generic Agent Harness",
|
|
2058
|
+
surface: "generic",
|
|
2059
|
+
defaultContextWindowTokens: 128e3,
|
|
2060
|
+
zeroHopBudgetPercent: 0.03,
|
|
2061
|
+
notes: "Fallback profile for unknown clients."
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
function isFirstPartyDomain(hostname) {
|
|
2065
|
+
const normalized = hostname.toLowerCase();
|
|
2066
|
+
return normalized.endsWith(".dev") && normalized.startsWith("stable");
|
|
2067
|
+
}
|
|
2068
|
+
function safeHostname(origin) {
|
|
2069
|
+
try {
|
|
2070
|
+
return new URL(origin).hostname;
|
|
2071
|
+
} catch {
|
|
2072
|
+
return origin.replace(/^https?:\/\//, "").split("/")[0] ?? origin;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function previewText(text) {
|
|
2076
|
+
if (!text) return null;
|
|
2077
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
2078
|
+
if (!compact) return null;
|
|
2079
|
+
return compact.length > 220 ? `${compact.slice(0, 217)}...` : compact;
|
|
2080
|
+
}
|
|
2081
|
+
function toAuthModeCount(resources) {
|
|
2082
|
+
const counts = {
|
|
2083
|
+
paid: 0,
|
|
2084
|
+
siwx: 0,
|
|
2085
|
+
apiKey: 0,
|
|
2086
|
+
unprotected: 0,
|
|
2087
|
+
unknown: 0
|
|
2088
|
+
};
|
|
2089
|
+
for (const resource of resources) {
|
|
2090
|
+
const mode = resource.authHint;
|
|
2091
|
+
if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
|
|
2092
|
+
counts[mode] += 1;
|
|
2093
|
+
} else {
|
|
2094
|
+
counts.unknown += 1;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return counts;
|
|
2098
|
+
}
|
|
2099
|
+
function toSourceCount(resources) {
|
|
2100
|
+
return resources.reduce(
|
|
2101
|
+
(acc, resource) => {
|
|
2102
|
+
acc[resource.source] = (acc[resource.source] ?? 0) + 1;
|
|
2103
|
+
return acc;
|
|
2104
|
+
},
|
|
2105
|
+
{}
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
function toL2Entries(resources) {
|
|
2109
|
+
return [...resources].sort((a, b) => {
|
|
2110
|
+
if (a.path !== b.path) return a.path.localeCompare(b.path);
|
|
2111
|
+
if (a.method !== b.method) return a.method.localeCompare(b.method);
|
|
2112
|
+
return a.resourceKey.localeCompare(b.resourceKey);
|
|
2113
|
+
}).map((resource) => ({
|
|
2114
|
+
resourceKey: resource.resourceKey,
|
|
2115
|
+
method: resource.method,
|
|
2116
|
+
path: resource.path,
|
|
2117
|
+
source: resource.source,
|
|
2118
|
+
authMode: resource.authHint ?? null
|
|
2119
|
+
}));
|
|
2120
|
+
}
|
|
2121
|
+
function getLlmsTxtInfo(result) {
|
|
2122
|
+
const llmsTxtUrl = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? null;
|
|
2123
|
+
const llmsTxt = result.rawSources?.llmsTxt;
|
|
2124
|
+
if (llmsTxt) {
|
|
2125
|
+
return {
|
|
2126
|
+
llmsTxtUrl,
|
|
2127
|
+
llmsTxtTokenEstimate: estimateTokenCount(llmsTxt),
|
|
2128
|
+
guidancePreview: previewText(llmsTxt),
|
|
2129
|
+
guidanceStatus: "present"
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
if (llmsTxtUrl) {
|
|
2133
|
+
const failed = result.warnings.some((warning2) => warning2.code === "LLMSTXT_FETCH_FAILED");
|
|
2134
|
+
return {
|
|
2135
|
+
llmsTxtUrl,
|
|
2136
|
+
llmsTxtTokenEstimate: 0,
|
|
2137
|
+
guidancePreview: null,
|
|
2138
|
+
guidanceStatus: failed ? "advertised_but_unfetched" : "missing"
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
return {
|
|
2142
|
+
llmsTxtUrl: null,
|
|
2143
|
+
llmsTxtTokenEstimate: 0,
|
|
2144
|
+
guidancePreview: null,
|
|
2145
|
+
guidanceStatus: "not_advertised"
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
function getHarnessClientProfile(client = "claude-code") {
|
|
2149
|
+
return CLIENT_PROFILES[client] ?? CLIENT_PROFILES.generic;
|
|
2150
|
+
}
|
|
2151
|
+
function buildContextHarnessReport(options) {
|
|
2152
|
+
const client = getHarnessClientProfile(options.client);
|
|
2153
|
+
const contextWindowTokens = options.contextWindowTokens ?? client.defaultContextWindowTokens;
|
|
2154
|
+
const zeroHopBudgetTokens = Math.floor(contextWindowTokens * client.zeroHopBudgetPercent);
|
|
2155
|
+
const hostname = safeHostname(options.result.origin);
|
|
2156
|
+
const domainClass = isFirstPartyDomain(hostname) ? "first-party" : "ugc";
|
|
2157
|
+
const l0Summary = "Route payment-capable intents to agentcash and use install/discover commands for progressive disclosure.";
|
|
2158
|
+
const l1Summary = "Expose installed domain routing hints and trigger fan-out into L2/L3 commands when user intent matches domain capabilities.";
|
|
2159
|
+
const l0EstimatedTokens = estimateTokenCount(
|
|
2160
|
+
`${INTENT_TRIGGERS.join(", ")} npx agentcash install npx agentcash install-ext`
|
|
2161
|
+
);
|
|
2162
|
+
const l1EstimatedTokens = estimateTokenCount(
|
|
2163
|
+
`${hostname} ${l1Summary} npx agentcash discover ${hostname} npx agentcash discover ${hostname} --verbose`
|
|
2164
|
+
);
|
|
2165
|
+
const llmsInfo = getLlmsTxtInfo(options.result);
|
|
2166
|
+
return {
|
|
2167
|
+
target: options.target,
|
|
2168
|
+
origin: options.result.origin,
|
|
2169
|
+
client,
|
|
2170
|
+
budget: {
|
|
2171
|
+
contextWindowTokens,
|
|
2172
|
+
zeroHopBudgetTokens,
|
|
2173
|
+
estimatedZeroHopTokens: l0EstimatedTokens + l1EstimatedTokens,
|
|
2174
|
+
withinBudget: l0EstimatedTokens + l1EstimatedTokens <= zeroHopBudgetTokens
|
|
2175
|
+
},
|
|
2176
|
+
levels: {
|
|
2177
|
+
l0: {
|
|
2178
|
+
layer: "L0",
|
|
2179
|
+
intentTriggers: INTENT_TRIGGERS,
|
|
2180
|
+
installCommand: "npx agentcash install",
|
|
2181
|
+
deliverySurfaces: ["MCP", "Skill+CLI"],
|
|
2182
|
+
summary: l0Summary,
|
|
2183
|
+
estimatedTokens: l0EstimatedTokens
|
|
2184
|
+
},
|
|
2185
|
+
l1: {
|
|
2186
|
+
layer: "L1",
|
|
2187
|
+
domain: hostname,
|
|
2188
|
+
domainClass,
|
|
2189
|
+
selectedDiscoveryStage: options.result.selectedStage ?? null,
|
|
2190
|
+
summary: l1Summary,
|
|
2191
|
+
fanoutCommands: [
|
|
2192
|
+
`npx agentcash discover ${hostname}`,
|
|
2193
|
+
`npx agentcash discover ${hostname} --verbose`
|
|
2194
|
+
],
|
|
2195
|
+
estimatedTokens: l1EstimatedTokens
|
|
2196
|
+
},
|
|
2197
|
+
l2: {
|
|
2198
|
+
layer: "L2",
|
|
2199
|
+
command: `npx agentcash discover ${hostname}`,
|
|
2200
|
+
tokenLight: true,
|
|
2201
|
+
resourceCount: options.result.resources.length,
|
|
2202
|
+
resources: toL2Entries(options.result.resources)
|
|
2203
|
+
},
|
|
2204
|
+
l3: {
|
|
2205
|
+
layer: "L3",
|
|
2206
|
+
command: `npx agentcash discover ${hostname} --verbose`,
|
|
2207
|
+
detailLevel: "endpoint-schema-and-metadata",
|
|
2208
|
+
countsByAuthMode: toAuthModeCount(options.result.resources),
|
|
2209
|
+
countsBySource: toSourceCount(options.result.resources),
|
|
2210
|
+
pricedResourceCount: options.result.resources.filter(
|
|
2211
|
+
(resource) => resource.authHint === "paid"
|
|
2212
|
+
).length
|
|
2213
|
+
},
|
|
2214
|
+
l4: {
|
|
2215
|
+
layer: "L4",
|
|
2216
|
+
guidanceSource: llmsInfo.llmsTxtUrl ? "llms.txt" : "none",
|
|
2217
|
+
llmsTxtUrl: llmsInfo.llmsTxtUrl,
|
|
2218
|
+
llmsTxtTokenEstimate: llmsInfo.llmsTxtTokenEstimate,
|
|
2219
|
+
guidancePreview: llmsInfo.guidancePreview,
|
|
2220
|
+
guidanceStatus: llmsInfo.guidanceStatus
|
|
2221
|
+
},
|
|
2222
|
+
l5: {
|
|
2223
|
+
layer: "L5",
|
|
2224
|
+
status: "out_of_scope",
|
|
2225
|
+
note: "Cross-domain composition is intentionally out of scope for discovery v1."
|
|
2226
|
+
}
|
|
2227
|
+
},
|
|
2228
|
+
diagnostics: {
|
|
2229
|
+
warningCount: options.result.warnings.length,
|
|
2230
|
+
errorWarningCount: options.result.warnings.filter((warning2) => warning2.severity === "error").length,
|
|
2231
|
+
selectedStage: options.result.selectedStage ?? null,
|
|
2232
|
+
upgradeSuggested: options.result.upgradeSuggested,
|
|
2233
|
+
upgradeReasons: options.result.upgradeReasons
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
|
|
1711
2238
|
// src/index.ts
|
|
1712
2239
|
async function discoverDetailed(options) {
|
|
1713
2240
|
return await runDiscovery({ detailed: true, options });
|
|
@@ -1758,7 +2285,7 @@ async function runAudit(options, deps = {}) {
|
|
|
1758
2285
|
headers: {
|
|
1759
2286
|
"user-agent": CLI_USER_AGENT
|
|
1760
2287
|
},
|
|
1761
|
-
rawView: "none"
|
|
2288
|
+
rawView: options.harness ? "full" : "none"
|
|
1762
2289
|
};
|
|
1763
2290
|
const startedAt = Date.now();
|
|
1764
2291
|
let result = await discoverDetailedFn(baseOptions);
|
|
@@ -1776,7 +2303,15 @@ async function runAudit(options, deps = {}) {
|
|
|
1776
2303
|
return {
|
|
1777
2304
|
result,
|
|
1778
2305
|
durationMs: Date.now() - startedAt,
|
|
1779
|
-
probeCandidateCount
|
|
2306
|
+
probeCandidateCount,
|
|
2307
|
+
...options.harness ? {
|
|
2308
|
+
harness: buildContextHarnessReport({
|
|
2309
|
+
target: options.target,
|
|
2310
|
+
result,
|
|
2311
|
+
client: options.client,
|
|
2312
|
+
contextWindowTokens: options.contextWindowTokens
|
|
2313
|
+
})
|
|
2314
|
+
} : {}
|
|
1780
2315
|
};
|
|
1781
2316
|
}
|
|
1782
2317
|
|