@kirrosh/zond 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -21
  3. package/bin/zond.mjs +23 -0
  4. package/package.json +13 -16
  5. package/scripts/npm/postinstall.mjs +76 -0
  6. package/src/CLAUDE.md +0 -112
  7. package/src/bun-types.d.ts +0 -5
  8. package/src/cli/argv.ts +0 -122
  9. package/src/cli/commands/add-api.ts +0 -146
  10. package/src/cli/commands/api/annotate/idempotency.ts +0 -59
  11. package/src/cli/commands/api/annotate/index.ts +0 -880
  12. package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
  13. package/src/cli/commands/api/annotate/overlay.ts +0 -206
  14. package/src/cli/commands/api/annotate/pagination.ts +0 -64
  15. package/src/cli/commands/api/annotate/prompts.ts +0 -220
  16. package/src/cli/commands/api/annotate/readback.ts +0 -58
  17. package/src/cli/commands/api/annotate/resources.ts +0 -91
  18. package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
  19. package/src/cli/commands/audit.ts +0 -786
  20. package/src/cli/commands/catalog.ts +0 -97
  21. package/src/cli/commands/check.ts +0 -361
  22. package/src/cli/commands/checks.ts +0 -1072
  23. package/src/cli/commands/ci-init.ts +0 -223
  24. package/src/cli/commands/clean.ts +0 -212
  25. package/src/cli/commands/cleanup.ts +0 -236
  26. package/src/cli/commands/completions.ts +0 -192
  27. package/src/cli/commands/coverage.ts +0 -872
  28. package/src/cli/commands/db.ts +0 -611
  29. package/src/cli/commands/describe.ts +0 -120
  30. package/src/cli/commands/discover.ts +0 -1356
  31. package/src/cli/commands/doctor.ts +0 -661
  32. package/src/cli/commands/fixtures.ts +0 -402
  33. package/src/cli/commands/generate.ts +0 -577
  34. package/src/cli/commands/init/agents-md.ts +0 -61
  35. package/src/cli/commands/init/bootstrap.ts +0 -111
  36. package/src/cli/commands/init/index.ts +0 -244
  37. package/src/cli/commands/init/skills.ts +0 -141
  38. package/src/cli/commands/init/templates/agents.md +0 -86
  39. package/src/cli/commands/init/templates/markdown.d.ts +0 -4
  40. package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
  41. package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
  42. package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
  43. package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
  44. package/src/cli/commands/init/templates/skills/zond.md +0 -861
  45. package/src/cli/commands/init/templates/zond-config.yml +0 -14
  46. package/src/cli/commands/prepare-fixtures.ts +0 -97
  47. package/src/cli/commands/probe/_seed-bodies.ts +0 -52
  48. package/src/cli/commands/probe/mass-assignment.ts +0 -594
  49. package/src/cli/commands/probe/security.ts +0 -537
  50. package/src/cli/commands/probe/static.ts +0 -255
  51. package/src/cli/commands/probe/webhooks.ts +0 -163
  52. package/src/cli/commands/probe.ts +0 -535
  53. package/src/cli/commands/reference.ts +0 -87
  54. package/src/cli/commands/refresh-api.ts +0 -227
  55. package/src/cli/commands/remove-api.ts +0 -150
  56. package/src/cli/commands/report-bundle.ts +0 -310
  57. package/src/cli/commands/report.ts +0 -241
  58. package/src/cli/commands/request.ts +0 -548
  59. package/src/cli/commands/run.ts +0 -1142
  60. package/src/cli/commands/schema-from-runs.ts +0 -128
  61. package/src/cli/commands/secrets.ts +0 -133
  62. package/src/cli/commands/session.ts +0 -244
  63. package/src/cli/commands/use.ts +0 -74
  64. package/src/cli/index.ts +0 -55
  65. package/src/cli/json-envelope.ts +0 -108
  66. package/src/cli/json-schemas.ts +0 -314
  67. package/src/cli/output.ts +0 -40
  68. package/src/cli/program.ts +0 -219
  69. package/src/cli/resolve.ts +0 -105
  70. package/src/cli/runtime.ts +0 -7
  71. package/src/cli/safe-live.ts +0 -24
  72. package/src/cli/status-filter.ts +0 -114
  73. package/src/cli/util/api-context.ts +0 -85
  74. package/src/cli/version.ts +0 -8
  75. package/src/core/audit/persist.ts +0 -183
  76. package/src/core/checks/budget.ts +0 -59
  77. package/src/core/checks/checks/_crud-helpers.ts +0 -133
  78. package/src/core/checks/checks/_negative_mutator.ts +0 -133
  79. package/src/core/checks/checks/_readback-helpers.ts +0 -133
  80. package/src/core/checks/checks/content_type_conformance.ts +0 -39
  81. package/src/core/checks/checks/cross_call_references.ts +0 -147
  82. package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
  83. package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
  84. package/src/core/checks/checks/idempotency_replay.ts +0 -242
  85. package/src/core/checks/checks/ignored_auth.ts +0 -254
  86. package/src/core/checks/checks/index.ts +0 -68
  87. package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
  88. package/src/core/checks/checks/missing_required_header.ts +0 -40
  89. package/src/core/checks/checks/negative_data_rejection.ts +0 -148
  90. package/src/core/checks/checks/not_a_server_error.ts +0 -35
  91. package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
  92. package/src/core/checks/checks/pagination_invariants.ts +0 -419
  93. package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
  94. package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
  95. package/src/core/checks/checks/response_headers_conformance.ts +0 -74
  96. package/src/core/checks/checks/response_schema_conformance.ts +0 -30
  97. package/src/core/checks/checks/status_code_conformance.ts +0 -132
  98. package/src/core/checks/checks/unsupported_method.ts +0 -63
  99. package/src/core/checks/checks/use_after_free.ts +0 -78
  100. package/src/core/checks/index.ts +0 -30
  101. package/src/core/checks/mode.ts +0 -82
  102. package/src/core/checks/recommended-action.ts +0 -68
  103. package/src/core/checks/registry.ts +0 -78
  104. package/src/core/checks/runner.ts +0 -1461
  105. package/src/core/checks/sarif.ts +0 -230
  106. package/src/core/checks/spec-findings.ts +0 -308
  107. package/src/core/checks/stateful.ts +0 -121
  108. package/src/core/checks/types.ts +0 -305
  109. package/src/core/checks/zond-extensions.ts +0 -73
  110. package/src/core/classifier/recommended-action.ts +0 -251
  111. package/src/core/context/current.ts +0 -51
  112. package/src/core/context/session.ts +0 -78
  113. package/src/core/coverage/loader.ts +0 -216
  114. package/src/core/coverage/reasons.ts +0 -300
  115. package/src/core/diagnostics/db-analysis.ts +0 -666
  116. package/src/core/diagnostics/failure-class.ts +0 -140
  117. package/src/core/diagnostics/failure-hints.ts +0 -112
  118. package/src/core/diagnostics/spec-pointer.ts +0 -99
  119. package/src/core/diagnostics/suggested-fixes.ts +0 -155
  120. package/src/core/exporter/case-study/index.ts +0 -270
  121. package/src/core/exporter/curl.ts +0 -40
  122. package/src/core/exporter/exporter.ts +0 -48
  123. package/src/core/exporter/html-report/escape.ts +0 -24
  124. package/src/core/exporter/html-report/index.ts +0 -479
  125. package/src/core/exporter/html-report/script.ts +0 -100
  126. package/src/core/exporter/html-report/styles.ts +0 -408
  127. package/src/core/generator/catalog-builder.ts +0 -179
  128. package/src/core/generator/chunker.ts +0 -78
  129. package/src/core/generator/coverage-phase.ts +0 -0
  130. package/src/core/generator/coverage-scanner.ts +0 -87
  131. package/src/core/generator/data-factory.ts +0 -759
  132. package/src/core/generator/describe.ts +0 -302
  133. package/src/core/generator/endpoint-warnings.ts +0 -52
  134. package/src/core/generator/fixtures-builder.ts +0 -332
  135. package/src/core/generator/index.ts +0 -14
  136. package/src/core/generator/openapi-reader.ts +0 -297
  137. package/src/core/generator/path-param-disambig.ts +0 -140
  138. package/src/core/generator/resources-builder.ts +0 -898
  139. package/src/core/generator/schema-utils.ts +0 -112
  140. package/src/core/generator/serializer.ts +0 -343
  141. package/src/core/generator/suite-generator.ts +0 -1301
  142. package/src/core/generator/types.ts +0 -63
  143. package/src/core/identity/identity-file.ts +0 -0
  144. package/src/core/lint/affects.ts +0 -28
  145. package/src/core/lint/config.ts +0 -96
  146. package/src/core/lint/format.ts +0 -42
  147. package/src/core/lint/index.ts +0 -94
  148. package/src/core/lint/reporter.ts +0 -128
  149. package/src/core/lint/rules/consistency.ts +0 -158
  150. package/src/core/lint/rules/heuristics.ts +0 -97
  151. package/src/core/lint/rules/strictness.ts +0 -109
  152. package/src/core/lint/types.ts +0 -96
  153. package/src/core/lint/walker.ts +0 -248
  154. package/src/core/meta/meta-store.ts +0 -11
  155. package/src/core/output/README.md +0 -73
  156. package/src/core/output/index.ts +0 -13
  157. package/src/core/output/run.ts +0 -91
  158. package/src/core/output/types.ts +0 -122
  159. package/src/core/parser/dynamic-values.ts +0 -160
  160. package/src/core/parser/env-interpolation.ts +0 -104
  161. package/src/core/parser/filter.ts +0 -97
  162. package/src/core/parser/schema.ts +0 -359
  163. package/src/core/parser/types.ts +0 -117
  164. package/src/core/parser/variables.ts +0 -0
  165. package/src/core/parser/yaml-parser.ts +0 -181
  166. package/src/core/probe/bootstrap.ts +0 -34
  167. package/src/core/probe/dry-run-envelope.ts +0 -61
  168. package/src/core/probe/mass-assignment/classify.ts +0 -175
  169. package/src/core/probe/mass-assignment/cleanup.ts +0 -52
  170. package/src/core/probe/mass-assignment/digest.ts +0 -114
  171. package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
  172. package/src/core/probe/mass-assignment/regression.ts +0 -141
  173. package/src/core/probe/mass-assignment/suspects.ts +0 -92
  174. package/src/core/probe/mass-assignment/types.ts +0 -135
  175. package/src/core/probe/mass-assignment-probe-class.ts +0 -198
  176. package/src/core/probe/mass-assignment-probe.ts +0 -27
  177. package/src/core/probe/mass-assignment-template.ts +0 -240
  178. package/src/core/probe/method-probe.ts +0 -164
  179. package/src/core/probe/method-shared.ts +0 -69
  180. package/src/core/probe/negative-probe.ts +0 -691
  181. package/src/core/probe/orphan-tracker.ts +0 -188
  182. package/src/core/probe/path-discovery.ts +0 -439
  183. package/src/core/probe/probe-harness.ts +0 -119
  184. package/src/core/probe/registry.ts +0 -89
  185. package/src/core/probe/runner.ts +0 -136
  186. package/src/core/probe/security/baseline.ts +0 -174
  187. package/src/core/probe/security/classify.ts +0 -341
  188. package/src/core/probe/security/cleanup.ts +0 -125
  189. package/src/core/probe/security/detectors.ts +0 -71
  190. package/src/core/probe/security/digest.ts +0 -104
  191. package/src/core/probe/security/orchestrator.ts +0 -398
  192. package/src/core/probe/security/regression.ts +0 -103
  193. package/src/core/probe/security/types.ts +0 -151
  194. package/src/core/probe/security-probe-class.ts +0 -207
  195. package/src/core/probe/security-probe.ts +0 -32
  196. package/src/core/probe/shared.ts +0 -531
  197. package/src/core/probe/static-probe-class.ts +0 -125
  198. package/src/core/probe/types.ts +0 -165
  199. package/src/core/probe/verdict-aggregator.ts +0 -33
  200. package/src/core/probe/webhooks-probe.ts +0 -282
  201. package/src/core/reporter/console.ts +0 -240
  202. package/src/core/reporter/index.ts +0 -22
  203. package/src/core/reporter/json.ts +0 -22
  204. package/src/core/reporter/junit.ts +0 -93
  205. package/src/core/reporter/ndjson.ts +0 -37
  206. package/src/core/reporter/types.ts +0 -15
  207. package/src/core/runner/assertions.ts +0 -383
  208. package/src/core/runner/async-pool.ts +0 -108
  209. package/src/core/runner/auth-path.ts +0 -8
  210. package/src/core/runner/ci-context.ts +0 -72
  211. package/src/core/runner/executor.ts +0 -652
  212. package/src/core/runner/expr-eval.ts +0 -41
  213. package/src/core/runner/form-encode.ts +0 -41
  214. package/src/core/runner/http-client.ts +0 -224
  215. package/src/core/runner/learn-drift.ts +0 -293
  216. package/src/core/runner/preflight-vars.ts +0 -153
  217. package/src/core/runner/progress-tracker.ts +0 -73
  218. package/src/core/runner/rate-limiter.ts +0 -185
  219. package/src/core/runner/run-kind.ts +0 -45
  220. package/src/core/runner/schema-validator.ts +0 -308
  221. package/src/core/runner/send-request.ts +0 -232
  222. package/src/core/runner/transforms.ts +0 -65
  223. package/src/core/runner/types.ts +0 -94
  224. package/src/core/secrets/registry.ts +0 -164
  225. package/src/core/secrets/secrets-file.ts +0 -115
  226. package/src/core/selectors/operation-filter.ts +0 -144
  227. package/src/core/setup-api.ts +0 -590
  228. package/src/core/severity/category.ts +0 -94
  229. package/src/core/severity/index.ts +0 -58
  230. package/src/core/spec/infer-schema.ts +0 -102
  231. package/src/core/spec/layers.ts +0 -154
  232. package/src/core/spec/merge-specs.ts +0 -156
  233. package/src/core/spec/schema-from-runs.ts +0 -117
  234. package/src/core/spec/schema-overlay.ts +0 -130
  235. package/src/core/util/ajv.ts +0 -13
  236. package/src/core/util/format-eta.ts +0 -21
  237. package/src/core/util/headers.ts +0 -9
  238. package/src/core/util/url.ts +0 -24
  239. package/src/core/utils.ts +0 -13
  240. package/src/core/workspace/config.ts +0 -129
  241. package/src/core/workspace/fixture-gap-report.ts +0 -84
  242. package/src/core/workspace/fixture-gaps.ts +0 -71
  243. package/src/core/workspace/manifest.ts +0 -283
  244. package/src/core/workspace/output-rotation.ts +0 -62
  245. package/src/core/workspace/root.ts +0 -96
  246. package/src/core/workspace/triage-path.ts +0 -87
  247. package/src/db/lint-runs.ts +0 -47
  248. package/src/db/migrate.ts +0 -128
  249. package/src/db/migrations/0001_run_kind.sql +0 -25
  250. package/src/db/migrations/0002_run_kind_request.sql +0 -59
  251. package/src/db/migrations/sql.d.ts +0 -4
  252. package/src/db/queries/collections.ts +0 -133
  253. package/src/db/queries/coverage.ts +0 -9
  254. package/src/db/queries/dashboard.ts +0 -59
  255. package/src/db/queries/results.ts +0 -216
  256. package/src/db/queries/runs.ts +0 -289
  257. package/src/db/queries/sessions.ts +0 -42
  258. package/src/db/queries/settings.ts +0 -28
  259. package/src/db/queries/types.ts +0 -172
  260. package/src/db/queries.ts +0 -75
  261. package/src/db/schema.ts +0 -298
@@ -1,535 +0,0 @@
1
- /**
2
- * Probe umbrella.
3
- *
4
- * TASK-182 (m-11) introduced `zond probe <class>` as the canonical entry
5
- * point. TASK-300 (m-13) consolidated the two static-input classes —
6
- * validation and methods — under `zond probe static [--include …]`; the
7
- * old `probe validation` / `probe methods` subcommands were removed
8
- * outright (no deprecation alias).
9
- *
10
- * Extracted from program.ts (TASK-190 round 2e) so the registration tree
11
- * lives next to the action functions it dispatches into.
12
- */
13
-
14
- import type { Command } from "commander";
15
-
16
- // ARV-129: action handlers relocated from top-level commands/probe-*.ts
17
- // into commands/probe/ — the orchestrator (this file) stays at top level,
18
- // the per-subcommand modules are no longer mistaken for siblings.
19
- import { probeStaticCommand, resolveStaticClasses } from "./probe/static.ts";
20
- import { probeMassAssignmentCommand, emitMassAssignmentTemplateCommand } from "./probe/mass-assignment.ts";
21
- import { probeSecurityCommand } from "./probe/security.ts";
22
- import { probeWebhooksCommand } from "./probe/webhooks.ts";
23
- import { SECURITY_CLASSES } from "../../core/probe/security-probe.ts";
24
- import { globalJson, resolveSpecArg, resolveApiEnv, resolveApiCollection } from "../resolve.ts";
25
- import { getApi } from "../util/api-context.ts";
26
- import { existsSync } from "fs";
27
- import { join, dirname } from "node:path";
28
- import { parsePositiveInt } from "../argv.ts";
29
- import { printError, printWarning } from "../output.ts";
30
- import { SAFE_HELP, LIVE_HELP, resolveLive } from "../safe-live.ts";
31
- import { jsonError, printJson } from "../json-envelope.ts";
32
- import { loadEnvMeta } from "../../core/parser/variables.ts";
33
- import { resolveTimeoutMs } from "../../core/workspace/config.ts";
34
- import { resolveOutput, OutputSpecError, type OutputSpec, type ResolvedOutput } from "../../core/output/index.ts";
35
-
36
- /**
37
- * ARV-119 (m-19): typed declaration of the `--report` / `--output`
38
- * surface shared by the live-probe subcommands (mass-assignment +
39
- * security). Both render a markdown digest by default, with `--report
40
- * json` switching to a structured JSON file body. `--output <path>`
41
- * routes the rendered body to a file; without it the body lands on
42
- * stdout (when `--json` is not set — see m-17 / ARV-51: the `--json`
43
- * envelope is a separate channel that wraps the structured result).
44
- *
45
- * `probe static` is *not* on this spec — its `--output` is a directory
46
- * where YAML probe suites are written, semantics not output-format.
47
- */
48
- export const PROBE_OUTPUT_SPEC: OutputSpec<unknown> = {
49
- command: "probe",
50
- defaultFormat: "markdown",
51
- formats: {
52
- markdown: { defaultChannel: "stdout", description: "Human-readable digest (default)" },
53
- json: { defaultChannel: "stdout", description: "Structured JSON body — same shape as the --json envelope's data." },
54
- },
55
- };
56
-
57
- /**
58
- * ARV-119: shared `--report` / `--output` / `--overwrite` option set
59
- * for the two live-probe subcommands. Resolution goes through
60
- * `resolveProbeOutputFlags` so unknown formats and mutually-exclusive
61
- * combinations surface the same error for both subcommands instead of
62
- * each one reimplementing the validation.
63
- */
64
- function addProbeReportOutputOptions(cmd: Command): Command {
65
- return cmd
66
- .option("--output <file>", "ARV-119: write the rendered report to this file (default: stdout). Pairs with --report to pick the format.")
67
- .option(
68
- "--report <format>",
69
- "ARV-119: format for --output / non-json stdout (markdown|json). Default markdown. The --json envelope (m-17 ARV-51) is a separate channel and is always structured.",
70
- "markdown",
71
- )
72
- .option("--overwrite", "Overwrite existing --output file in place (default: rotate to <stem>-vN.<ext>)");
73
- }
74
-
75
- interface ProbeReportOutputOpts {
76
- report?: string;
77
- output?: string;
78
- }
79
-
80
- /**
81
- * ARV-119: resolve --report / --output through PROBE_OUTPUT_SPEC. On
82
- * unknown format / mutual-exclusion violation, prints the consistent
83
- * error (envelope when --json) and returns null — caller exits 2.
84
- */
85
- function resolveProbeOutputFlags(
86
- command: string,
87
- opts: ProbeReportOutputOpts,
88
- json: boolean,
89
- ): { resolved: ResolvedOutput; report: "markdown" | "json"; output?: string } | null {
90
- let resolved: ResolvedOutput;
91
- try {
92
- resolved = resolveOutput(PROBE_OUTPUT_SPEC, { report: opts.report, output: opts.output });
93
- } catch (err) {
94
- if (err instanceof OutputSpecError) {
95
- if (json) printJson(jsonError(command, [err.message]));
96
- else printError(err.message);
97
- return null;
98
- }
99
- throw err;
100
- }
101
- const report: "markdown" | "json" = resolved.format === "json" ? "json" : "markdown";
102
- const output = resolved.channel === "file" ? resolved.path : undefined;
103
- return { resolved, report, output };
104
- }
105
-
106
- /**
107
- * ARV-53: thin wrapper kept for the existing call-sites — the real chain
108
- * (local → ancestor → ZOND_API_GLOBAL/ZOND_API/.zond/current-api) lives in
109
- * cli/util/api-context.ts. `resolveProbeApi` predates that helper (ARV-33).
110
- */
111
- export function resolveProbeApi(
112
- optsApi: string | undefined,
113
- cmd: { opts?: () => Record<string, unknown>; parent?: { opts(): Record<string, unknown> } | null } | undefined,
114
- ): string | undefined {
115
- // Adapt the loose mock shape used at the old call-sites to CommandLike.
116
- if (cmd === undefined) {
117
- return getApi(undefined, { api: optsApi });
118
- }
119
- const adapted = {
120
- opts: () => (typeof cmd.opts === "function" ? cmd.opts() : {}),
121
- parent: cmd.parent ?? null,
122
- };
123
- return getApi(adapted, { api: optsApi });
124
- }
125
-
126
- /**
127
- * TASK-233: pick the env file to feed live-probe commands.
128
- * - Explicit --env wins (legacy behaviour).
129
- * - Otherwise --api <name> derives `apis/<name>/.env.yaml` via the registered
130
- * collection's base_dir.
131
- * - With `tolerateMissing` (probe security/--dry-run), an absent file is
132
- * quietly turned into "no env" — the command will fall back to cwd.
133
- */
134
- function resolveProbeEnv(
135
- envFlag: string | undefined,
136
- apiFlag: string | undefined,
137
- dbPath: string | undefined,
138
- opts: { tolerateMissing?: boolean } = {},
139
- ): { env: string | undefined } | { error: string } {
140
- if (envFlag) return { env: envFlag };
141
- if (!apiFlag) {
142
- if (opts.tolerateMissing) return { env: undefined };
143
- return { error: "Missing --env <file> (or pass --api <name> to derive it from apis/<name>/.env.yaml)" };
144
- }
145
- const resolved = resolveApiEnv(apiFlag, dbPath);
146
- if ("error" in resolved) return resolved;
147
- if (!existsSync(resolved.env)) {
148
- if (opts.tolerateMissing) return { env: undefined };
149
- return { error: `Env file not found: ${resolved.env} (derived from --api ${apiFlag})` };
150
- }
151
- return { env: resolved.env };
152
- }
153
-
154
- /**
155
- * Resolve `--timeout` for live-probe commands. Reads the per-API
156
- * `.env.yaml` `timeoutMs:` meta when `--api` is set (or when the env
157
- * file is on disk), then falls back to workspace `defaults.timeout_ms`.
158
- */
159
- async function resolveProbeTimeout(
160
- cliFlag: number | undefined,
161
- apiFlag: string | undefined,
162
- envFile: string | undefined,
163
- ): Promise<number> {
164
- let envTimeout: number | undefined;
165
- try {
166
- if (apiFlag) {
167
- const meta = await loadEnvMeta(undefined, `apis/${apiFlag}`);
168
- envTimeout = meta.timeoutMs;
169
- } else if (envFile) {
170
- const meta = await loadEnvMeta(undefined, dirname(envFile));
171
- envTimeout = meta.timeoutMs;
172
- }
173
- } catch { /* meta is best-effort */ }
174
- return resolveTimeoutMs(cliFlag, envTimeout);
175
- }
176
-
177
- function defineProbeStatic(parent: Command, name: string): void {
178
- parent
179
- .command(`${name} [spec]`)
180
- .description(
181
- "Generate static-input probe suites (validation: bogus types/values; methods: undeclared HTTP methods). Defaults to both; restrict via --include or --exclude.",
182
- )
183
- .option("--api <name>", "Use the registered API's spec (apis/<name>/spec.json)")
184
- .option("--db <path>", "Path to SQLite database file")
185
- // ARV-30: --output is optional when --api (or current-api) is set —
186
- // probes land in apis/<name>/probes/static/ alongside generate's tests/.
187
- // Required only when probing a bare spec with no registered collection.
188
- .option("--output <dir>", "Output directory for generated probe files (default: apis/<api>/probes/static when --api / current-api is set)")
189
- .option("--tag <tag>", "Probe only endpoints with this tag")
190
- .option("--list-tags", "List available tags from spec and exit")
191
- .option("--max-per-endpoint <N>", "Cap negative-input probes per endpoint (default 50)", parsePositiveInt("--max-per-endpoint"))
192
- .option("--no-cleanup", "Skip emission of follow-up DELETE cleanup steps for mutating probes (use in namespace-isolated test envs)")
193
- .option("--use-synthetic-parents", "Bake synthetic-by-type values into all path params (legacy). By default, non-attacked path params are emitted as {{name}} and resolved from .env.yaml at run time — needed to reach the leaf validator on nested paths (TASK-135).")
194
- // ARV-225: probe static --include is a CLASS LIST ({validation, methods}),
195
- // while sibling commands (probe security, probe mass-assignment, checks
196
- // run) use SELECTOR grammar (path:/method:/tag:/operation-id:). Rename the
197
- // canonical flag to --include-class / --exclude-class for clarity; keep
198
- // --include / --exclude as deprecated aliases (warn on use).
199
- .option("--include-class <classes>", "Comma-separated subset of {validation, methods} (default: both)")
200
- .option("--exclude-class <classes>", "Comma-separated subset to skip (mutually exclusive with --include-class)")
201
- .option("--include <classes>", "[deprecated, use --include-class] Comma-separated subset of {validation, methods}")
202
- .option("--exclude <classes>", "[deprecated, use --exclude-class] Comma-separated subset to skip")
203
- // ARV-299: static only *generates* probe suites (it never sends live
204
- // traffic), so it is always safe. The flags exist for vocabulary parity
205
- // with the sibling subcommands; --live is a no-op here and says so.
206
- .option("--safe", SAFE_HELP)
207
- .option("--live", LIVE_HELP)
208
- .action(async (specPos: string | undefined, optsArg, cmdRef: Command) => {
209
- if (optsArg.live === true) {
210
- printWarning("probe static only generates suites and never sends live traffic — --safe/--live have no effect here. Run the emitted suites with `zond run … --live` to execute them.");
211
- }
212
- // ARV-33: see resolveProbeApi — keep the chain consistent with the
213
- // sibling subcommands (mass-assignment, security).
214
- const apiName = resolveProbeApi(optsArg.api, cmdRef);
215
- const resolved = resolveSpecArg(specPos, apiName, optsArg.db);
216
- if ("error" in resolved) { printError(resolved.error); process.exitCode = 2; return; }
217
-
218
- // ARV-225: prefer --include-class / --exclude-class. Fall back to
219
- // --include / --exclude with a one-line stderr deprecation note —
220
- // these are the legacy names that collide semantically with the
221
- // selector --include on sibling commands.
222
- let includeClasses: string | undefined = optsArg.includeClass;
223
- let excludeClasses: string | undefined = optsArg.excludeClass;
224
- if (!includeClasses && optsArg.include) {
225
- process.stderr.write(
226
- "Warning: `probe static --include <classes>` is deprecated (class-list, not a selector — collides with probe security / checks run). Use --include-class.\n",
227
- );
228
- includeClasses = optsArg.include;
229
- }
230
- if (!excludeClasses && optsArg.exclude) {
231
- process.stderr.write(
232
- "Warning: `probe static --exclude <classes>` is deprecated. Use --exclude-class.\n",
233
- );
234
- excludeClasses = optsArg.exclude;
235
- }
236
- const r = resolveStaticClasses(includeClasses, excludeClasses);
237
- if ("error" in r) { printError(r.error); process.exitCode = 2; return; }
238
-
239
- // ARV-30: derive --output from the registered API's base_dir when the
240
- // user didn't pass one. Bare-spec invocations (positional only, no --api,
241
- // no current-api) still must pass --output explicitly.
242
- let outputDir: string | undefined = optsArg.output;
243
- if (!outputDir && apiName) {
244
- const col = resolveApiCollection(apiName, optsArg.db);
245
- if (!("error" in col) && col.baseDir) outputDir = join(col.baseDir, "probes", "static");
246
- }
247
- if (!outputDir) {
248
- printError("--output <dir> is required when no --api / current-api can resolve apis/<name>/probes/static.");
249
- process.exitCode = 2;
250
- return;
251
- }
252
-
253
- const useReal = optsArg.useSyntheticParents !== true;
254
- process.exitCode = await probeStaticCommand({
255
- specPath: resolved.spec,
256
- output: outputDir,
257
- tag: optsArg.tag,
258
- maxPerEndpoint: optsArg.maxPerEndpoint,
259
- noCleanup: optsArg.cleanup === false,
260
- useRealParents: useReal,
261
- json: globalJson(cmdRef),
262
- listTags: optsArg.listTags,
263
- include: r.classes,
264
- });
265
- });
266
- }
267
-
268
- function defineProbeMassAssignment(parent: Command, name: string): void {
269
- const sub = parent
270
- .command(`${name} [spec]`)
271
- .description(
272
- "Live probe for mass-assignment / privilege-escalation: classifies POST/PATCH/PUT against suspected extra fields (is_admin, role, account_id, owner_id, user_id, verified, is_system) as rejected (4xx) | accepted-and-applied (HIGH) | accepted-and-ignored (LOW) via follow-up GET",
273
- )
274
- .option("--api <name>", "Use the registered API's spec (apis/<name>/spec.json)")
275
- .option("--db <path>", "Path to SQLite database file")
276
- .option("--env <file>", "Env YAML with base_url + auth_token (live calls require this; auto-derived from apis/<name>/.env.yaml when --api is given)")
277
- .option("--emit-tests <dir>", "Also emit YAML regression suites locking in safe behaviour for CI")
278
- .option("--tag <tag>", "Probe only endpoints with this tag")
279
- .option("--list-tags", "List available tags from spec and exit")
280
- .option("--no-cleanup", "Skip follow-up DELETE for resources accidentally created by 2xx probes")
281
- .option("--no-discover", "Disable auto-discovery of path-param fixtures via GET-on-list (TASK-92)")
282
- .option("--dry-run", "Print which endpoints/fields would be attacked without sending requests (m-17 ARV-52). Equivalent to the default --safe mode.")
283
- .option("--safe", SAFE_HELP)
284
- .option("--live", LIVE_HELP)
285
- .option(
286
- "--include <selector>",
287
- "Filter operations (m-15 ARV-9 grammar: path:/users/.* | tag:Webhooks | method:POST,PATCH | operation-id:create.*). Repeatable.",
288
- (v: string, prev: string[] = []) => prev.concat(v),
289
- [] as string[],
290
- )
291
- .option(
292
- "--exclude <selector>",
293
- "Drop operations matching <selector>. Repeatable. Same grammar as --include.",
294
- (v: string, prev: string[] = []) => prev.concat(v),
295
- [] as string[],
296
- )
297
- .option("--timeout <ms>", "Per-request timeout in ms (overrides apis/<name>/.env.yaml `timeoutMs` and zond.config.yml `defaults.timeout_ms`; default 30000)", parsePositiveInt("--timeout"))
298
- .option(
299
- "--verbose",
300
- "ARV-252: surface INFO-severity inconclusive verdicts (absent-but-unverifiable). Silently-ignored verdicts (correct framework behaviour) stay hidden even with this flag — they're never finding-worthy.",
301
- )
302
- .option(
303
- "--suspect-field <name=value>",
304
- "ARV-252: extend the curated suspect-fields list (is_admin, role, owner_id, …) with a custom field. Repeatable. Full per-api spec-extension support tracked in ARV-189.",
305
- (v: string, prev: string[] = []) => prev.concat(v),
306
- [] as string[],
307
- )
308
- .option("--emit-template <method:path>", "TASK-146: emit a ready-to-edit YAML probe template for one endpoint (e.g. \"POST:/users\") instead of running the live probe. Pairs `--output <file>` to write to disk (default: stdout). Use to drop down to manual catch-up after INCONCLUSIVE / INCONCLUSIVE-5XX verdicts without copy-pasting boilerplate from the skill.")
309
- .option("--max-endpoints <n>", "ARV-302: cap the number of endpoints probed in this run (after --include / --exclude / --tag filters). Used by `zond audit --budget` to keep probe stages inside a wall-clock budget instead of unbounded scanning.", parsePositiveInt("--max-endpoints"));
310
- addProbeReportOutputOptions(sub);
311
- sub.action(async (specPos: string | undefined, opts, cmd: Command) => {
312
- // ARV-33: resolve --api via the same fallback chain as prepare-fixtures /
313
- // ARV-29 — direct opts, then parent opts, then ZOND_API_GLOBAL /
314
- // .zond/current-api. Otherwise `zond probe mass-assignment --api foo`
315
- // hits commander's global-option absorption and `opts.api` is empty.
316
- const apiName = resolveProbeApi(opts.api, cmd);
317
- const resolved = resolveSpecArg(specPos, apiName, opts.db);
318
- if ("error" in resolved) { printError(resolved.error); process.exitCode = 2; return; }
319
-
320
- // --emit-template short-circuits the live probe.
321
- if (opts.emitTemplate) {
322
- process.exitCode = await emitMassAssignmentTemplateCommand({
323
- specPath: resolved.spec,
324
- methodPath: opts.emitTemplate,
325
- output: opts.output,
326
- json: globalJson(cmd),
327
- });
328
- return;
329
- }
330
-
331
- // ARV-299: safe (default) → plan only, no live mutating traffic. The
332
- // proven --dry-run path IS the safe path, so we just force it on when
333
- // --live is absent (and say so, unless the user already asked for a
334
- // dry-run explicitly).
335
- const live = resolveLive(opts);
336
- const effectiveDryRun = opts.dryRun === true || !live;
337
- // ARV-348: fail loudly (see probe security) — --emit-tests needs live
338
- // verdicts; safe/dry-run mode would leave an empty dir + exit 0.
339
- if (opts.emitTests && !live) {
340
- printError("--emit-tests requires --live: safe/dry-run mode produces no live verdicts to lock into regression suites. Re-run with --live against a throwaway/sandbox account, or drop --emit-tests to just plan.");
341
- process.exitCode = 2;
342
- return;
343
- }
344
- if (!live && opts.dryRun !== true && !opts.listTags) {
345
- printWarning("probe mass-assignment: safe mode (default) — planning only, no live attack traffic. Re-run with --live against a throwaway/sandbox account to send probes.");
346
- }
347
- // m-17 / ARV-52 + ARV-58: dry-run and list-tags paths tolerate a
348
- // missing env file the way probe-security does — the user wants
349
- // to inspect the plan / available tags, not hit a live API.
350
- const envFile = resolveProbeEnv(opts.env, apiName, opts.db, {
351
- tolerateMissing: effectiveDryRun || opts.listTags === true,
352
- });
353
- if ("error" in envFile) { printError(envFile.error); process.exitCode = 2; return; }
354
- const timeoutMs = await resolveProbeTimeout(opts.timeout, apiName, envFile.env);
355
- const json = globalJson(cmd);
356
- const rep = resolveProbeOutputFlags("probe-mass-assignment", opts, json);
357
- if (!rep) { process.exitCode = 2; return; }
358
- process.exitCode = await probeMassAssignmentCommand({
359
- specPath: resolved.spec,
360
- env: envFile.env,
361
- apiName,
362
- output: rep.output,
363
- emitTests: opts.emitTests,
364
- tag: opts.tag,
365
- listTags: opts.listTags,
366
- noCleanup: opts.cleanup === false,
367
- noDiscover: opts.discover === false,
368
- timeoutMs,
369
- overwrite: opts.overwrite === true,
370
- json,
371
- dryRun: effectiveDryRun,
372
- include: Array.isArray(opts.include) && opts.include.length > 0 ? opts.include : undefined,
373
- exclude: Array.isArray(opts.exclude) && opts.exclude.length > 0 ? opts.exclude : undefined,
374
- report: rep.report,
375
- verbose: opts.verbose === true,
376
- suspectField: Array.isArray(opts.suspectField) && opts.suspectField.length > 0 ? opts.suspectField : undefined,
377
- maxEndpoints: typeof opts.maxEndpoints === "number" ? opts.maxEndpoints : undefined,
378
- });
379
- });
380
- }
381
-
382
- function defineProbeSecurity(parent: Command, name: string): void {
383
- const sub = parent
384
- // ARV-36: classes is technically required but kept optional in commander
385
- // so the missing-arg branch can produce the same actionable list of
386
- // available classes that --unknown-class already prints (data already in
387
- // SECURITY_CLASSES — no reason to force a --help read for first-time users).
388
- .command(`${name} [classes] [spec]`)
389
- .description(
390
- "Live security probes (TASK-138): SSRF / CRLF / open-redirect. Detects vulnerable fields by name+format, sends a baseline-OK then per-field payloads, classifies HIGH (5xx or echo) / LOW (2xx no echo) / OK (4xx). <classes> is a comma-separated subset of: ssrf, crlf, open-redirect.",
391
- )
392
- .option("--api <name>", "Use the registered API's spec (apis/<name>/spec.json)")
393
- .option("--db <path>", "Path to SQLite database file")
394
- .option("--env <file>", "Env YAML with base_url + auth_token (live calls require this; --dry-run can run without; auto-derived from apis/<name>/.env.yaml when --api is given)")
395
- .option("--emit-tests <dir>", "Also emit YAML regression suites locking in safe behaviour for CI")
396
- .option("--tag <tag>", "Probe only endpoints with this tag")
397
- .option("--list-tags", "List available tags from spec and exit")
398
- .option("--no-cleanup", "Skip follow-up DELETE on resources created by baseline / 2xx attacks")
399
- .option("--isolated", "TASK-264: refuse to attack PUT/PATCH endpoints whose path-params come from .env.yaml — protects seeded fixtures from probe-induced mutation. Lower coverage in exchange for guaranteed fixture safety.")
400
- .option("--allow-leaks", "ARV-140: attack POST endpoints even when the spec has no DELETE counterpart. Default: skip — without DELETE there is no cleanup path and resources accumulate in the target tenant (round-01/02 Sentry left 18 manual orphans). Use when you've vetted manual cleanup or are in a throwaway env.")
401
- .option(
402
- "--include <selector>",
403
- "Filter operations (m-15 ARV-9 grammar: path:/users/.* | tag:Webhooks | method:POST,PATCH). Repeatable.",
404
- (v: string, prev: string[] = []) => prev.concat(v),
405
- [] as string[],
406
- )
407
- .option(
408
- "--exclude <selector>",
409
- "Drop operations matching <selector>. Repeatable.",
410
- (v: string, prev: string[] = []) => prev.concat(v),
411
- [] as string[],
412
- )
413
- .option("--dry-run", "Print which endpoints/fields would be attacked without sending requests. Equivalent to the default --safe mode.")
414
- .option("--safe", SAFE_HELP)
415
- .option("--live", LIVE_HELP)
416
- .option("--timeout <ms>", "Per-request timeout in ms (overrides apis/<name>/.env.yaml `timeoutMs` and zond.config.yml `defaults.timeout_ms`; default 30000)", parsePositiveInt("--timeout"))
417
- .option(
418
- "--verbose",
419
- "ARV-253: surface INFO-severity findings (sanitization-signal-only, e.g. CRLF accepted but no reflection observed). Default hides them — they're single-signal proof with no exploit pathway.",
420
- )
421
- .option("--max-endpoints <n>", "ARV-302: cap the number of endpoints probed in this run (after --include / --exclude / --tag filters). Used by `zond audit --budget` to keep probe stages inside a wall-clock budget instead of unbounded scanning.", parsePositiveInt("--max-endpoints"));
422
- addProbeReportOutputOptions(sub);
423
- sub.action(async (classes: string | undefined, specPos: string | undefined, opts, cmd: Command) => {
424
- // ARV-36: missing-arg path should list the available classes (parity
425
- // with the unknown-class error). Commander's default `missing required
426
- // argument` doesn't include them; once we made <classes> optional, we
427
- // surface the same hint here.
428
- if (typeof classes !== "string" || classes.length === 0) {
429
- printError(`Missing required argument <classes>. Available: ${SECURITY_CLASSES.join(", ")}`);
430
- process.exitCode = 2;
431
- return;
432
- }
433
- // ARV-33: same fallback chain as mass-assignment so `zond probe security
434
- // ssrf --api foo` doesn't fall through to a confusing "base_url is
435
- // required" when commander absorbs --api at the global scope.
436
- const apiName = resolveProbeApi(opts.api, cmd);
437
- const resolved = resolveSpecArg(specPos, apiName, opts.db);
438
- if ("error" in resolved) { printError(resolved.error); process.exitCode = 2; return; }
439
- // ARV-299: safe (default) forces plan-only; live attack traffic needs --live.
440
- const live = resolveLive(opts);
441
- const effectiveDryRun = opts.dryRun === true || !live;
442
- // ARV-348: fail loudly instead of silently producing an empty dir. Safe/
443
- // dry-run mode has no live verdicts to lock into regression suites, so
444
- // --emit-tests can't do anything — and a later `zond run <dir>` on the
445
- // empty dir just prints "No test files found". One consistent message
446
- // (—live), not the old contradictory "Re-run without --dry-run".
447
- if (opts.emitTests && !live) {
448
- printError("--emit-tests requires --live: safe/dry-run mode produces no live verdicts to lock into regression suites. Re-run with --live against a throwaway/sandbox account, or drop --emit-tests to just plan.");
449
- process.exitCode = 2;
450
- return;
451
- }
452
- if (!live && opts.dryRun !== true && !opts.listTags) {
453
- printWarning("probe security: safe mode (default) — planning only, no live attack traffic. Re-run with --live against a throwaway/sandbox account to send probes.");
454
- }
455
- // probe-security tolerates a missing env (--dry-run path), so don't
456
- // fail when --api is given but the env file isn't on disk yet.
457
- const envFile = resolveProbeEnv(opts.env, apiName, opts.db, { tolerateMissing: true });
458
- if ("error" in envFile) { printError(envFile.error); process.exitCode = 2; return; }
459
- const timeoutMs = await resolveProbeTimeout(opts.timeout, apiName, envFile.env);
460
- const json = globalJson(cmd);
461
- const rep = resolveProbeOutputFlags("probe-security", opts, json);
462
- if (!rep) { process.exitCode = 2; return; }
463
- process.exitCode = await probeSecurityCommand({
464
- specPath: resolved.spec,
465
- classes,
466
- env: envFile.env,
467
- output: rep.output,
468
- emitTests: opts.emitTests,
469
- tag: opts.tag,
470
- listTags: opts.listTags,
471
- noCleanup: opts.cleanup === false,
472
- dryRun: effectiveDryRun,
473
- timeoutMs,
474
- overwrite: opts.overwrite === true,
475
- json,
476
- apiName,
477
- isolated: opts.isolated === true,
478
- allowLeaks: opts.allowLeaks === true,
479
- report: rep.report,
480
- include: Array.isArray(opts.include) && opts.include.length > 0 ? opts.include : undefined,
481
- exclude: Array.isArray(opts.exclude) && opts.exclude.length > 0 ? opts.exclude : undefined,
482
- verbose: opts.verbose === true,
483
- maxEndpoints: typeof opts.maxEndpoints === "number" ? opts.maxEndpoints : undefined,
484
- });
485
- });
486
- }
487
-
488
- /**
489
- * ARV-173 (m-20): `zond probe webhooks` — offline shape-conformance for
490
- * webhook events captured by `docs/recipes/webhook-receiver.md`.
491
- *
492
- * Live HTTP infrastructure (tunnels, listeners) lives in the recipe,
493
- * not in core zond. The CLI takes a pre-captured ndjson log + the
494
- * API's spec and validates each event's payload against
495
- * `spec.webhooks.<event>.post.requestBody`. Same recipe/probe split as
496
- * m-18's quicktype and interactsh.
497
- */
498
- function defineProbeWebhooks(parent: Command, name: string): void {
499
- const sub = parent
500
- .command(`${name} [spec]`)
501
- .description("Shape-conform captured webhook events (ndjson) against spec.webhooks. Recipe: docs/recipes/webhook-receiver.md")
502
- .option("--api <name>", "Use the registered API's spec (apis/<name>/spec.json)")
503
- .option("--db <path>", "Path to SQLite database file")
504
- .requiredOption("--event-log <file>", "ndjson event log captured by the recipe (one JSON event per line)")
505
- .option("--only <types>", "Comma-separated event types to validate (default: all declared)");
506
- addProbeReportOutputOptions(sub);
507
- sub.action(async (specPos: string | undefined, opts, cmd: Command) => {
508
- const apiName = resolveProbeApi(opts.api, cmd);
509
- const resolved = resolveSpecArg(specPos, apiName, opts.db);
510
- if ("error" in resolved) { printError(resolved.error); process.exitCode = 2; return; }
511
- const json = globalJson(cmd);
512
- const rep = resolveProbeOutputFlags("probe-webhooks", opts, json);
513
- if (!rep) { process.exitCode = 2; return; }
514
- process.exitCode = await probeWebhooksCommand({
515
- specPath: resolved.spec,
516
- eventLog: opts.eventLog,
517
- only: opts.only,
518
- report: rep.report,
519
- output: rep.output,
520
- json,
521
- apiName,
522
- });
523
- });
524
- }
525
-
526
- export function registerProbes(program: Command): void {
527
- const probeCmd = program
528
- .command("probe")
529
- .description("Run a probe class — pick one of: static, mass-assignment, security, webhooks");
530
-
531
- defineProbeStatic(probeCmd, "static");
532
- defineProbeMassAssignment(probeCmd, "mass-assignment");
533
- defineProbeSecurity(probeCmd, "security");
534
- defineProbeWebhooks(probeCmd, "webhooks");
535
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * `zond reference <topic>` — printable cheat-sheets for built-ins that aren't
3
- * surfaced anywhere else (TASK-267).
4
- *
5
- * Currently:
6
- * reference random-helpers # all `{{$random*}}` generators with examples
7
- *
8
- * Designed to be discoverable from `--help` AND scriptable: `--json` returns
9
- * an array of `{ name, example }` entries so the `zond-triage` / generator
10
- * skills can prompt with concrete values.
11
- */
12
- import type { Command } from "commander";
13
- import { GENERATORS } from "../../core/parser/variables.ts";
14
- import { jsonOk, printJson } from "../json-envelope.ts";
15
- import { globalJson } from "../resolve.ts";
16
-
17
- interface HelperEntry {
18
- name: string;
19
- example: string;
20
- use_for: string;
21
- }
22
-
23
- const HELPER_NOTES: Record<string, string> = {
24
- "$uuid": "RFC 4122 v4 — Idempotency-Key, opaque resource ids",
25
- "$timestamp": "UNIX seconds — created_at, monotonic seeds",
26
- "$isoTimestamp": "RFC 3339 timestamps",
27
- "$randomName": "display names",
28
- "$randomEmail": "unique e-mail body fields",
29
- "$randomInt": "0–9999, small numeric ids",
30
- "$randomString": "8 chars mixed-case + digits — opaque tokens",
31
- "$randomSlug": "8 chars lowercase + digits — slug / handle / URL-safe ids",
32
- "$randomUrl": "webhook / callback URL fields",
33
- "$randomFqdn": "DNS / hostname inputs",
34
- "$randomDomain": "alias of $randomFqdn",
35
- "$randomIpv4": "RFC 1918 range — client_ip / source_ip",
36
- "$randomDate": "calendar dates (YYYY-MM-DD)",
37
- "$randomIsoDate": "ISO-8601 datetime",
38
- "$nullByte": "single space — placeholder for fields that reject empty strings",
39
- };
40
-
41
- function collectEntries(): HelperEntry[] {
42
- return Object.keys(GENERATORS)
43
- .sort()
44
- .map((name) => ({
45
- name,
46
- example: String(GENERATORS[name]!()),
47
- use_for: HELPER_NOTES[name] ?? "",
48
- }));
49
- }
50
-
51
- function renderTable(entries: HelperEntry[]): string {
52
- const nameWidth = Math.max(8, ...entries.map((e) => e.name.length));
53
- const exampleWidth = Math.max(10, ...entries.map((e) => e.example.length));
54
- const lines: string[] = [];
55
- lines.push(`${pad("Helper", nameWidth)} ${pad("Example", exampleWidth)} Use for`);
56
- lines.push(`${"-".repeat(nameWidth)} ${"-".repeat(exampleWidth)} ${"-".repeat(20)}`);
57
- for (const e of entries) {
58
- lines.push(`${pad(`{{${e.name}}}`, nameWidth + 4)} ${pad(e.example, exampleWidth)} ${e.use_for}`);
59
- }
60
- return lines.join("\n");
61
- }
62
-
63
- function pad(s: string, width: number): string {
64
- return s.length >= width ? s : s + " ".repeat(width - s.length);
65
- }
66
-
67
- export function registerReference(program: Command): void {
68
- const ref = program
69
- .command("reference")
70
- .description("Printable references for built-ins (e.g. `random-helpers`).");
71
-
72
- ref
73
- .command("random-helpers")
74
- .description("List every `{{$random*}}` / `{{$uuid}}` / `{{$timestamp}}` helper, with a sample value and typical use. Pair `--json` for machine-readable output (TASK-267).")
75
- .action((opts: unknown, cmd: Command) => {
76
- const entries = collectEntries();
77
- if (globalJson(cmd)) {
78
- printJson(jsonOk("reference random-helpers", { helpers: entries }));
79
- return;
80
- }
81
- process.stdout.write(renderTable(entries) + "\n");
82
- process.stdout.write(
83
- "\nFor field-name → helper mapping used by `zond generate`, see docs/random-helpers.md.\n",
84
- );
85
- void opts;
86
- });
87
- }