@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/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]).slice(0, 8).map(([code]) => code);
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:${resource.pricing.price ?? "?"}`;
270
+ return resource.pricing.price ? `fixed ${resource.pricing.price}` : "fixed";
194
271
  }
195
272
  if (resource.pricing.pricingMode === "range") {
196
- return `range:${resource.pricing.minPrice ?? "?"}-${resource.pricing.maxPrice ?? "?"}`;
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 asYesNo(value) {
203
- return value ? "yes" : "no";
279
+ function statusBadge(ok, palette) {
280
+ return ok ? palette.green("[PASS]") : palette.red("[FAIL]");
204
281
  }
205
- function createTable(headers, rows) {
206
- const widths = headers.map((header, idx) => {
207
- const cellWidths = rows.map((row) => row[idx]?.length ?? 0);
208
- return Math.max(header.length, ...cellWidths);
209
- });
210
- const line = (row) => row.map((cell, idx) => {
211
- const value = cell ?? "";
212
- return value.padEnd(widths[idx], " ");
213
- }).join(" | ");
214
- const separator = widths.map((width) => "-".repeat(width)).join("-|-");
215
- return [line(headers), separator, ...rows.map((row) => line(row))].join("\n");
216
- }
217
- function stageRows(result) {
218
- return result.trace.map((entry) => {
219
- const uniqueWarningCodes = [...new Set(entry.warnings.map((warning2) => warning2.code))];
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
- uniqueWarningCodes.length > 0 ? uniqueWarningCodes.join(",") : "-",
226
- result.selectedStage === entry.stage ? "yes" : "-"
370
+ result.selectedStage === entry.stage ? "yes" : "-",
371
+ codes || "-"
227
372
  ];
228
373
  });
374
+ return [header, ...rows];
229
375
  }
230
- function resourceRows(result) {
231
- return [...result.resources].sort((a, b) => {
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
- }).map((resource) => [
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 renderHeader(run, options, palette, withVerboseSuffix) {
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
- `${palette.bold("agentcash discovery audit")}${withVerboseSuffix ? ` ${palette.cyan("(verbose)")}` : ""}`,
252
- `target: ${options.target}`,
253
- `origin: ${run.result.origin}`,
254
- `status: ${status}`,
255
- `resources: ${run.result.resources.length}`,
256
- `selected_stage: ${run.result.selectedStage ?? "none"}`,
257
- `compat_mode: ${mode}`,
258
- `upgrade_suggested: ${run.result.upgradeSuggested ? palette.yellow("yes") : "no"}`,
259
- `duration_ms: ${run.durationMs}`,
260
- `warnings: total=${warningSummary.total} error=${warningSummary.errors} warn=${warningSummary.warns} info=${warningSummary.infos}`,
261
- warningSummary.codes.length > 0 ? `warning_codes: ${warningSummary.codes.join(", ")}` : "warning_codes: -",
262
- options.probe ? `probe_candidates: ${run.probeCandidateCount}` : "probe_candidates: disabled"
263
- ].join("\n");
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
- return renderHeader(run, options, palette, false);
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
- const parts = [renderHeader(run, options, palette, true), ""];
272
- parts.push(palette.bold("stage_matrix"));
273
- parts.push(
274
- createTable(
275
- ["stage", "valid", "resources", "duration", "warning_codes", "selected"],
276
- stageRows(run.result)
277
- )
278
- );
279
- parts.push("");
280
- parts.push(palette.bold("resource_matrix"));
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
- parts.push(palette.dim("(no discovered resources)"));
584
+ body.push(palette.dim("(no discovered resources)"));
283
585
  } else {
284
- parts.push(
285
- createTable(
286
- ["method", "path", "auth", "pricing", "source", "verified", "notes"],
287
- resourceRows(run.result)
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
- parts.push("");
293
- parts.push(palette.bold("upgrade_reasons"));
605
+ body.push("");
606
+ body.push(sectionHeader("Upgrade guidance", palette));
294
607
  for (const reason of run.result.upgradeReasons) {
295
- parts.push(`- ${reason}`);
608
+ body.push(`- ${reason}`);
296
609
  }
297
610
  }
298
- return parts.join("\n");
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