@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.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]).slice(0, 8).map(([code]) => code);
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:${resource.pricing.price ?? "?"}`;
244
+ return resource.pricing.price ? `fixed ${resource.pricing.price}` : "fixed";
168
245
  }
169
246
  if (resource.pricing.pricingMode === "range") {
170
- return `range:${resource.pricing.minPrice ?? "?"}-${resource.pricing.maxPrice ?? "?"}`;
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 asYesNo(value) {
177
- return value ? "yes" : "no";
253
+ function statusBadge(ok, palette) {
254
+ return ok ? palette.green("[PASS]") : palette.red("[FAIL]");
178
255
  }
179
- function createTable(headers, rows) {
180
- const widths = headers.map((header, idx) => {
181
- const cellWidths = rows.map((row) => row[idx]?.length ?? 0);
182
- return Math.max(header.length, ...cellWidths);
183
- });
184
- const line = (row) => row.map((cell, idx) => {
185
- const value = cell ?? "";
186
- return value.padEnd(widths[idx], " ");
187
- }).join(" | ");
188
- const separator = widths.map((width) => "-".repeat(width)).join("-|-");
189
- return [line(headers), separator, ...rows.map((row) => line(row))].join("\n");
190
- }
191
- function stageRows(result) {
192
- return result.trace.map((entry) => {
193
- const uniqueWarningCodes = [...new Set(entry.warnings.map((warning2) => warning2.code))];
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
- uniqueWarningCodes.length > 0 ? uniqueWarningCodes.join(",") : "-",
200
- result.selectedStage === entry.stage ? "yes" : "-"
344
+ result.selectedStage === entry.stage ? "yes" : "-",
345
+ codes || "-"
201
346
  ];
202
347
  });
348
+ return [header, ...rows];
203
349
  }
204
- function resourceRows(result) {
205
- return [...result.resources].sort((a, b) => {
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
- }).map((resource) => [
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 renderHeader(run, options, palette, withVerboseSuffix) {
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
- `${palette.bold("agentcash discovery audit")}${withVerboseSuffix ? ` ${palette.cyan("(verbose)")}` : ""}`,
226
- `target: ${options.target}`,
227
- `origin: ${run.result.origin}`,
228
- `status: ${status}`,
229
- `resources: ${run.result.resources.length}`,
230
- `selected_stage: ${run.result.selectedStage ?? "none"}`,
231
- `compat_mode: ${mode}`,
232
- `upgrade_suggested: ${run.result.upgradeSuggested ? palette.yellow("yes") : "no"}`,
233
- `duration_ms: ${run.durationMs}`,
234
- `warnings: total=${warningSummary.total} error=${warningSummary.errors} warn=${warningSummary.warns} info=${warningSummary.infos}`,
235
- warningSummary.codes.length > 0 ? `warning_codes: ${warningSummary.codes.join(", ")}` : "warning_codes: -",
236
- options.probe ? `probe_candidates: ${run.probeCandidateCount}` : "probe_candidates: disabled"
237
- ].join("\n");
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
- return renderHeader(run, options, palette, false);
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
- const parts = [renderHeader(run, options, palette, true), ""];
246
- parts.push(palette.bold("stage_matrix"));
247
- parts.push(
248
- createTable(
249
- ["stage", "valid", "resources", "duration", "warning_codes", "selected"],
250
- stageRows(run.result)
251
- )
252
- );
253
- parts.push("");
254
- parts.push(palette.bold("resource_matrix"));
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
- parts.push(palette.dim("(no discovered resources)"));
558
+ body.push(palette.dim("(no discovered resources)"));
257
559
  } else {
258
- parts.push(
259
- createTable(
260
- ["method", "path", "auth", "pricing", "source", "verified", "notes"],
261
- resourceRows(run.result)
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
- parts.push("");
267
- parts.push(palette.bold("upgrade_reasons"));
579
+ body.push("");
580
+ body.push(sectionHeader("Upgrade guidance", palette));
268
581
  for (const reason of run.result.upgradeReasons) {
269
- parts.push(`- ${reason}`);
582
+ body.push(`- ${reason}`);
270
583
  }
271
584
  }
272
- return parts.join("\n");
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