@wacht/bench 0.1.3 → 0.1.4

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/commands.js CHANGED
@@ -16,6 +16,7 @@ import { completionScript } from './completion.js';
16
16
  import { configApply, configDiff, configPull, configSchemaCommand, printConfigTemplate, } from './config-workflow.js';
17
17
  import { clearDeployment, createDeploymentCommand, createProjectCommand, currentDeployment, selectDeployment, } from './deployment-context.js';
18
18
  import { initProject, initStarter } from './init.js';
19
+ import { docsSearch } from './docs-search.js';
19
20
  import { envPull } from './env-pull.js';
20
21
  import { apiCommand, listProjects } from './machine-api.js';
21
22
  import { openApiCall, openApiDescribe, openApiList, openApiRefresh } from './openapi.js';
@@ -219,6 +220,21 @@ export async function runCli(args) {
219
220
  .action((options) => {
220
221
  printMcpConfig(options.client);
221
222
  });
223
+ const docs = program.command('docs').description('search and explore Wacht docs');
224
+ docs
225
+ .command('search <query...>')
226
+ .description('full-text search Wacht docs and print matching pages')
227
+ .option('--limit <n>', 'max number of pages to print', (v) => Number.parseInt(v, 10))
228
+ .option('--base-url <url>', 'docs base URL (default: https://wacht.dev/docs, or $WACHT_DOCS_URL)')
229
+ .option('--json', 'emit JSON instead of human-readable output')
230
+ .action(async (queryParts, options) => {
231
+ await docsSearch(context(program), {
232
+ query: queryParts.join(' '),
233
+ limit: options.limit,
234
+ baseUrl: options.baseUrl,
235
+ json: options.json,
236
+ });
237
+ });
222
238
  const env = program.command('env').description('manage deployment credentials and environment files');
223
239
  env
224
240
  .command('pull')
@@ -0,0 +1,81 @@
1
+ import { field, log, printBannerFor, printError, printJson, section } from './ui.js';
2
+ const DEFAULT_DOCS_URL = 'https://wacht.dev/docs';
3
+ export async function docsSearch(ctx, options) {
4
+ const baseUrl = (options.baseUrl ?? process.env.WACHT_DOCS_URL ?? DEFAULT_DOCS_URL).replace(/\/+$/, '');
5
+ const endpoint = `${baseUrl}/api/search?query=${encodeURIComponent(options.query)}`;
6
+ let response;
7
+ try {
8
+ response = await fetch(endpoint, { headers: { Accept: 'application/json' } });
9
+ }
10
+ catch (error) {
11
+ printError(error);
12
+ process.exitCode = 1;
13
+ return;
14
+ }
15
+ if (!response.ok) {
16
+ printError(new Error(`docs search failed: ${response.status} ${response.statusText} (${endpoint})`));
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ const raw = (await response.json());
21
+ if (!Array.isArray(raw)) {
22
+ printError(new Error('unexpected docs search response shape'));
23
+ process.exitCode = 1;
24
+ return;
25
+ }
26
+ const pages = new Map();
27
+ const snippets = new Map();
28
+ for (const hit of raw) {
29
+ const pageUrl = hit.url.split('#')[0];
30
+ if (hit.type === 'page') {
31
+ pages.set(pageUrl, hit);
32
+ }
33
+ else {
34
+ if (!snippets.has(pageUrl))
35
+ snippets.set(pageUrl, []);
36
+ snippets.get(pageUrl).push(hit);
37
+ }
38
+ }
39
+ const orderedPages = Array.from(pages.values());
40
+ const sliced = options.limit ? orderedPages.slice(0, options.limit) : orderedPages;
41
+ if (options.json) {
42
+ printJson({
43
+ ok: true,
44
+ query: options.query,
45
+ baseUrl,
46
+ results: sliced.map((page) => ({
47
+ title: page.content,
48
+ url: `${baseUrl}${page.url}`,
49
+ breadcrumbs: page.breadcrumbs ?? [],
50
+ snippets: (snippets.get(page.url) ?? []).map((s) => ({
51
+ text: s.content,
52
+ url: `${baseUrl}${s.url}`,
53
+ })),
54
+ })),
55
+ });
56
+ return;
57
+ }
58
+ printBannerFor(ctx);
59
+ log(ctx, section('Docs Search'));
60
+ log(ctx, field('Query', options.query));
61
+ log(ctx, field('Source', baseUrl));
62
+ log(ctx, field('Results', String(sliced.length)));
63
+ log(ctx, '');
64
+ if (sliced.length === 0) {
65
+ log(ctx, 'No matches.');
66
+ return;
67
+ }
68
+ for (const page of sliced) {
69
+ const breadcrumbs = page.breadcrumbs?.length ? page.breadcrumbs.join(' › ') : '';
70
+ log(ctx, `• ${page.content}`);
71
+ if (breadcrumbs)
72
+ log(ctx, ` ${breadcrumbs}`);
73
+ log(ctx, ` ${baseUrl}${page.url}`);
74
+ const pageSnippets = snippets.get(page.url) ?? [];
75
+ for (const s of pageSnippets.slice(0, 3)) {
76
+ const trimmed = s.content.length > 140 ? `${s.content.slice(0, 137).trimEnd()}…` : s.content;
77
+ log(ctx, ` – ${trimmed}`);
78
+ }
79
+ log(ctx, '');
80
+ }
81
+ }
@@ -20,22 +20,8 @@ export function validateBody(spec, schema, body) {
20
20
  return null;
21
21
  return summariseErrors(compiled.errors ?? [], body);
22
22
  }
23
- /**
24
- * The raw ajv error stream is noisy:
25
- * - `anyOf` / `oneOf` umbrellas restate "doesn't match" without specifics.
26
- * - The `type: null` branches our `nullable` rewrite introduces are never what
27
- * the user is trying to send.
28
- * - oneOf-with-discriminator emits per-variant required errors from every
29
- * non-matching branch, drowning out the actual problem.
30
- *
31
- * Strategy:
32
- * 1. Drop nullable + anyOf/oneOf umbrellas.
33
- * 2. When an `oneOf` umbrella was present at a path, collapse all required/const
34
- * errors under that path into a single "doesn't match any variant" line with
35
- * the discriminator value found in the body (if any). Tell the user to run
36
- * `wacht api describe` for the variant payload — avoids guessing wrong.
37
- * 3. Pass everything else through unchanged.
38
- */
23
+ // Collapse `oneOf` failures into one line per union and drop the noisy umbrella
24
+ // + `type: null` errors our `nullable` rewrite introduces.
39
25
  function summariseErrors(rawErrors, body) {
40
26
  // Discover oneOf failure points by looking for the `oneOf` umbrella error.
41
27
  const oneOfPaths = new Set();
@@ -72,13 +58,6 @@ function summariseErrors(rawErrors, body) {
72
58
  // Dedupe identical lines (e.g. two variants both miss the same field).
73
59
  return [...new Set(out)];
74
60
  }
75
- /**
76
- * When a `oneOf` discriminated union mismatches, ajv emits a "must be equal to constant"
77
- * error for every variant whose const failed, plus the required-field errors from each
78
- * variant's payload schema. That's overwhelming. If the input has a discriminator value
79
- * that matches *no* variant, surface a single "X must be one of [...]"; if it matches
80
- * exactly one, drop the per-variant required errors from the other variants.
81
- */
82
61
  function readJsonPointer(value, pointer) {
83
62
  if (!pointer || pointer === '/')
84
63
  return value;
package/dist/openapi.js CHANGED
@@ -119,6 +119,8 @@ function schemaType(schema) {
119
119
  return schema.anyOf.map((item) => isRecord(item) ? schemaType(item) : 'unknown').join(' | ');
120
120
  if (Array.isArray(schema.oneOf))
121
121
  return schema.oneOf.map((item) => isRecord(item) ? schemaType(item) : 'unknown').join(' | ');
122
+ if (schema.const !== undefined)
123
+ return `const ${JSON.stringify(schema.const)}`;
122
124
  return 'object';
123
125
  }
124
126
  const REF_PREFIX = '#/components/schemas/';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wacht/bench",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "CLI for Wacht Bench, the AI development workbench for Wacht.",
5
5
  "type": "module",
6
6
  "bin": {