vimanam 1.6.0

OpenAPI/Swagger to Markdown documentation generator with grouping, filtering, and detail levels for docs and LLM context
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
# Vimanam

[![CI](https://github.com/noemaforge/vimanam/actions/workflows/ci.yml/badge.svg)](https://github.com/noemaforge/vimanam/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/vimanam.svg)](https://crates.io/crates/vimanam)
[![License: Apache-2.0](https://img.shields.io/crates/l/vimanam.svg)](LICENSE)
[![MSRV](https://img.shields.io/badge/MSRV-1.96-blue.svg)](Cargo.toml)

Vimanam is a Rust CLI and library that turns OpenAPI/Swagger specs (JSON or YAML) into focused Markdown documentation and compares API contracts.

Vimanam stands for Aeroplane in Malayalam. Like an aeroplane, it can fly high and give you a 20,000 feet view of the APIs. It can fly low and give you a detailed view of the APIs. You can also run it along the ground to look deep into the API fields and descriptions.

It supports OpenAPI 2.0 (Swagger) and OpenAPI 3.0 specifications, with [partial OpenAPI 3.1 support](#supported-openapi-versions).

Besides producing documentation for humans, Vimanam is built for **feeding API specs to LLMs**: filter to the operations and fields you need, or generate a linked Skill tree that an agent can read on demand. See [Preparing API context for LLMs](#preparing-api-context-for-llms). Release history is in the [Changelog](CHANGELOG.md).

## Rust library

Use the parser, typed diff, versioned JSON contract, and focused Markdown renderer
in-process without the CLI dependencies:

```toml
[dependencies]
vimanam = { version = "1.6", default-features = false }
```

```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let old = vimanam::parse_openapi("old.json")?;
    let new = vimanam::parse_openapi("new.json")?;
    let changes = vimanam::diff::diff(&old, &new);

    let mut context = vimanam::DocConfig::unfiltered();
    context.operation_selector = Some(vimanam::OperationSelector {
        operations: changes.changes.iter()
            .map(|change| vimanam::OperationRef {
                method: change.endpoint.method.clone(),
                path: change.endpoint.path.clone(),
            })
            .filter(|op| new.endpoints.iter().any(|endpoint| op.matches(endpoint)))
            .collect(),
        ..Default::default()
    });
    context.max_tokens = Some(2000);
    let mut markdown = Vec::new();
    vimanam::markdown::generate_markdown(&mut markdown, &new, &context)?;
    std::fs::write("changed-api.md", markdown)?;
    Ok(())
}
```

This renders changed operations that still exist in the new spec. Render removed
operations from the old spec instead. `DocConfig::unfiltered()` starts with full
detail and schemas; a token budget may lower operation detail to fit its estimate.

`parse_openapi_bytes` accepts already-read bytes so hashing and parsing can use
the same input. `diff::json::to_json` returns the JSON contract types, which
support both serialization and deserialization. The library writes only to
caller-provided writers, logs through `log`, and optionally returns rendering
notices through `generate_markdown_with_notices`. The default `cli` feature
keeps normal `cargo install vimanam` behavior.

See the [library API reference](https://docs.rs/vimanam) for configuration and types.

## Features

- Convert OpenAPI JSON or YAML files to Markdown documentation (format detected by `.json`/`.yaml`/`.yml` extension, with automatic fallback)
- Supports OpenAPI 2.0 (Swagger), OpenAPI 3.0 and supported OpenAPI 3.1 schema fields
- Group endpoints by service, HTTP method, or path, or list them flat
- Filter by service, path, or method
- Select exact operations by method/path or operation ID (`--operation`, `--operation-id`)
- Embed parsing, typed diffs and focused rendering in Rust tools without CLI dependencies
- Multiple detail levels (summary, basic, standard, full)
- Token-budget-aware output (`--max-tokens`): steps the detail level down until the rendering fits, and reports what was trimmed on stderr
- Linked directory output (`--split service|tag|endpoint`): a compact overview alongside requested detail pages and shared schema files, with an independent overview budget
- Agent navigation (`--output-mode skill`): a compact `SKILL.md`, service/operation/schema hubs and individual-file read costs
- Token-budget dry run (`--stats`): a per-service table of endpoint counts and estimated token sizes, for sizing slices before choosing filters
- Token-cost analysis (`--costs`): per-endpoint slice costs with their share of TOTAL, per-schema definition and modeled inline-expansion costs with reference amplification, and hotspot rankings
- Spec diffing (`vimanam diff old.json new.json`): compares two versions of a spec on *resolved* schemas — a change behind a shared `$ref` is reported on every endpoint that uses it — and classifies each change as breaking, non-breaking or needing review, with an exit code for CI (`--fail-on-breaking`)
- Spec hygiene report for endpoint documentation (`--no-report` to skip): counts and lists operations missing a description, `operationId` or responses, deprecated and untagged operations, duplicate `operationId`s, and undescribed parameters
- Schema expansion at `--detail full --include-schemas`: renders request/response schemas as nested field tables. Shared component schemas are expanded once into a trailing "Schema Definitions" section and linked from each use site, keeping output compact when schemas are reused across endpoints; `--inline-schemas` instead expands every `$ref` inline at each use site (larger, fully self-contained, with cycle detection)
- Schema reads for oversized graphs: `--schema NAME` and `--schema-field NAME#POINTER` render one schema or subtree with its metadata, and `--schema-depth N` bounds expansion without dropping the selected fields
- Example rendering at `--detail full --include-examples`: emits request/response examples as fenced JSON blocks, resolving `$ref`s into `components/examples`
- Server URL information extraction and documentation
- Authentication and security schemes documentation
- Proper content type detection for responses
- Sorting options for endpoints (alphabetical, path length)
- Clean anchor generation for better navigation
- Deterministic, byte-identical output across runs — friendly to diffs, caching, and LLM prompt caching

## Installation

### Homebrew (macOS / Linux)

```bash
brew install noemaforge/tap/vimanam
```

No Rust toolchain required. Supports macOS (Apple Silicon & Intel) and x86_64 Linux.

### Install script (no toolchain)

```bash
# macOS / Linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/noemaforge/vimanam/releases/latest/download/vimanam-installer.sh | sh
```

```powershell
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://github.com/noemaforge/vimanam/releases/latest/download/vimanam-installer.ps1 | iex"
```

### From crates.io

```bash
cargo binstall vimanam   # prebuilt binary, no compile (needs cargo-binstall)
cargo install vimanam    # builds from source
```

### Prebuilt binaries

Download the archive for your platform (Linux, macOS Intel/ARM64, Windows) from the
[latest release](https://github.com/noemaforge/vimanam/releases/latest) — no Rust toolchain needed.
Archives are named `vimanam-<target-triple>.tar.xz` (`.zip` on Windows) and ship with a matching
`.sha256` checksum; each bundles the binary, `README.md`, `CHANGELOG.md`, and `LICENSE`. Extract it
and put the `vimanam` binary on your `PATH`.

### From source

Requires [Rust](https://www.rust-lang.org/tools/install) 1.96.0 or later.

```bash
# Clone repository
git clone https://github.com/noemaforge/vimanam.git
cd vimanam

# Build and run during development
cargo run -- input.json -o output.md

# Build and run
cargo build --release
./target/release/vimanam input.json -o output.md

# Or install system-wide
cargo install --path .
```

### Development

```bash
cargo test
cargo fmt && cargo clippy
```

Integration tests live in `tests/cli/`, `tests/split.rs` and `tests/library.rs` and run against `tests/fixtures/`. Use `cargo test --no-default-features` to exercise the core library without the CLI. `scripts/compare-baseline.sh` compares a fresh build with a released binary across the CLI matrix — stdout, stderr, exit code, and written files — so a refactor can be checked for byte-for-byte output parity. Setup and the case list are in [`scripts/README.md`](scripts/README.md).

### Shell completions

`vimanam completions <SHELL>` prints a completion script for `bash`, `zsh`, `fish`,
`powershell`, or `elvish`. Install it once and re-run after upgrading Vimanam:

```bash
# Bash (relies on the bash-completion v2 package, which stock macOS bash 3.2
# lacks; alternatively add `eval "$(vimanam completions bash)"` to ~/.bashrc)
mkdir -p ~/.local/share/bash-completion/completions
vimanam completions bash > ~/.local/share/bash-completion/completions/vimanam

# Zsh (make sure ~/.zfunc is on fpath before compinit runs in ~/.zshrc:
#   fpath+=~/.zfunc; autoload -Uz compinit; compinit)
mkdir -p ~/.zfunc
vimanam completions zsh > ~/.zfunc/_vimanam

# Fish
mkdir -p ~/.config/fish/completions
vimanam completions fish > ~/.config/fish/completions/vimanam.fish
```

```elvish
# Elvish: add this line to ~/.config/elvish/rc.elv
eval (vimanam completions elvish | slurp)
```

```powershell
# PowerShell: add this line to $PROFILE
vimanam completions powershell | Out-String | Invoke-Expression
```

## Usage

```bash
# Basic usage
vimanam input.json -o output.md

# Group by HTTP method
vimanam input.json --method -o output.md

# Group by path (one section per path, methods listed underneath)
vimanam input.json --group-by path -o output.md

# Generate summary only
vimanam input.json --detail summary -o output.md

# Fit the output to a token budget, stepping detail down as needed
vimanam input.json --detail full --max-tokens 8000 -o output.md

# Filter by specific services
vimanam input.json --service-filter Auth,Users -o output.md

# Filter by HTTP method
vimanam input.json --method-filter GET,POST -o output.md

# Show only paths containing a pattern
vimanam input.json --path-filter /api/v1 -o output.md

# Generate full details
vimanam input.json --detail full --include-schemas --include-examples -o output.md

# Include server and authentication information
vimanam input.json --include-auth -o output.md

# Print a shell completion script (see "Shell completions" above)
vimanam completions zsh

# Drop the spec hygiene report appended after the documentation
vimanam input.json --no-report -o output.md

# Size each service before choosing filters: endpoint counts and ~tokens per service
vimanam input.json --stats --detail standard

# Find the most expensive endpoints and schemas before choosing filters
vimanam input.json --costs --detail full --include-schemas

# Compare two versions of a spec; exit 3 if anything breaking changed
vimanam diff v1/openapi.json v2/openapi.json --report --fail-on-breaking
```

### Schema reads and expansion limits

Keep full split/Skill files available and request a smaller schema read when one file is too large:

```sh
vimanam spec.json --schema v1FindingSpec
vimanam spec.json --schema-field 'v1FindingSpec#/properties/finding_tags'
vimanam spec.json --schema-field 'v1FindingSpec#/properties/finding_tags/items/properties/tag'
vimanam spec.json --operation-id GetFinding --detail full --include-schemas --schema-depth 2
```

`--schema` and `--schema-field` are repeatable standalone reads. They always render full schema metadata and omit the endpoint hygiene report. Named schemas need not be reachable from an operation. Combining an exact operation selector adds a matched-operation context list; schema selection stays independent. Field reads retain ancestor types, descriptions, requiredness and enum values, pruning unrelated siblings and their references. A pointer crosses references automatically and selects schema subtrees using `/properties/NAME`, `/items`, `/additionalProperties`, or `/allOf/INDEX`, `/oneOf/INDEX`, `/anyOf/INDEX`. Escape property-name `~` as `~0` and `/` as `~1`; an empty pointer selects the complete schema. Invalid names/pointers fail before creating output. Both Swagger 2 definitions and OpenAPI 3 component schemas are supported.

`--schema-depth N` accepts 0 through 24, matching the recursion safety limit. A root has depth zero; each property, array item, composition variant, additional-properties schema and reference traversal adds one edge. At depth N, the row retains its type/reference, description and requiredness, but stops nested expansion and gives a full retrieval command. Depth zero keeps root metadata. Shared definitions inherit the shortest depth from the original roots, including across deferred definitions and split/Skill pages; limits never restart at every schema. Explicit field selectors protect their ancestor path and leaf metadata even below this depth; the limit then bounds expansion beyond that selection. Omissions are reported in Markdown and stderr.

Without standalone selectors, depth requires `--detail full --include-schemas`; it works with operation filters, `--stats` and split/Skill output. Selectors produce dedicated reads and conflict with split/Skill output and service-oriented `--stats`; use the commands in schema detail pages to read a field separately. Depth/field limits are opt-in; existing full rendering remains available when omitted.

For explicit schema reads, `--max-tokens` never drops selected metadata or lowers detail. If the selection exceeds the approximate characters/4 budget (including budget zero), Vimanam emits the complete requested read with an over-budget notice in Markdown and stderr. Narrow the field selector or explicitly set depth to reduce it. Other single-file output keeps its existing detail fallback, and split/Skill overview budgets affect only the overview.

### Token cost analysis

`--costs` sizes API context before reading it. It prints a plain-text report of
estimated token costs (chars/4 over real renders — never tokenizer counts or
billing figures) at the configured detail level, filters and schema mode:

```sh
vimanam input.json --costs --detail full --include-schemas
```

- **TOTAL** is one render of the whole filtered document — the same estimate
  `--max-tokens` would measure.
- **ENDPOINTS** rows are one render per endpoint alone: its service section,
  the document frame and the schema definitions it links. Rows overlap through
  the frame and shared schemas, so they neither sum to nor subtract from
  TOTAL. A multi-tag endpoint is measured once, under its first service (or
  the first one a `--service-filter` keeps); a service-grouped document
  renders it once per tag. A ` (+tag)` suffix marks the extra tags, rendered
  verbatim after the operation label.
- **SCHEMAS** rows show the definition cost (read once in linked mode), the
  use-site count, and — as a separate, clearly-labeled analysis — the modeled
  inline-expansion cost (uses × one measured, cycle-guarded expansion) with an
  amplification ratio. Uses are the linked render's rows: body rows plus the
  reference rows inside each rendered definition. Definition-internal rows have
  no inline counterpart, while a schema referenced only inside another
  definition expands with it, so the model can differ from real
  `--inline-schemas` output in either direction. Under `--inline-schemas`, uses
  are instead the expansion sites of the analyzed inline render, and the
  definition cost is the hypothetical linked-mode one (inline mode emits no
  definitions). Cyclic schemas are noted (they
  participate in a reference cycle; inline expansion cuts it with a one-row
  notice).
- **HOTSPOTS** rank the most expensive endpoint slices and the most amplifying
  schemas (by amplification ratio, not absolute size); shares are of TOTAL and
  overlap with each other.

Like `--stats`, it is a dry run: the hygiene report is never included, and it
conflicts with `-o`, `--max-tokens`, split/Skill output, `--stats` and schema
selection (`--schema`/`--schema-field`). `--inline-schemas` is allowed and
switches the analyzed mode.

## Spec hygiene report

Single-file endpoint documentation appends a short report, separated by a horizontal rule, that flags common gaps in the spec: operations with no summary or description, no `operationId`, no documented responses, deprecated operations, operations with no tag (attributed to the default service), duplicate `operationId`s, and parameters without a description (a request body counts once per operation, however many media types it offers). It covers the same endpoints the documentation does, so service, path, method and exact-operation filters, plus `--exclude-deprecated`, narrow the report too. Detail lists appear only for checks that found something. Split/Skill output writes it to `report.md`; schema selections, `--stats`, `--costs`, and the library Markdown renderer do not append it.

```markdown
---

## Spec Hygiene Report

**6 endpoints** across **2 services**

| Check | Count |
|-------|------:|
| Missing description | 1 |
| Missing operationId | 1 |
| No responses documented | 1 |
| Deprecated | 2 |
| Untagged (no service tag) | 1 |
| Duplicate operationIds | 1 |
| Parameters without description | 3 |

### Missing description (1)
- `GET /health`

### Deprecated (2)
- `GET /ping`
- `DELETE /users/{id}`

### Duplicate operationIds (1)
- `getUser`
  - `DELETE /users/{id}`
  - `GET /users/{id}`

### Parameters without description (3)
- `GET /users` — `limit`
- `POST /users` — `requestBody`
- `DELETE /users/{id}` — `id`
```

Pass `--no-report` to omit it. The report is not counted against `--max-tokens` — the budget fits the documentation body only — so combine `--max-tokens` with `--no-report` when the whole output must stay within the budget.

## Linked pages with a compact overview

Keep complete API detail available on disk while loading only the relevant pages:

```bash
vimanam openapi.json --split endpoint -o ./api-docs \
  --detail full --include-schemas --include-examples --overview-max-tokens 2000
```

Start at `api-docs/index.md`. Its entries show method/path, operation ID and a brief description, and link to detail pages. The linked `api.md` retains the complete API description and global usage guidance outside the overview budget. Endpoint detail pages link to shared `schemas/*.md` files; each reachable schema is rendered once, including cyclic references. `--split service` produces one detail page per service, and `--split tag` uses the spec's tags (the parser's service names). Multi-tag operations appear under each selected service/tag. Endpoint splitting produces one page per operation. Filtering applies to the generated tree; omitted operations are disclosed in the overview with a command to retrieve the full tree.

`--detail` controls detail pages; the overview stays compact independently. `--overview-max-tokens` uses the approximate chars/4 estimate only for `index.md`. When operation entries do not fit, `index-all.md` retains complete navigation. Even a tiny budget preserves the navigation notice and can therefore exceed the estimate. Linked detail pages keep the requested detail level and schema tables. The hygiene report, when enabled, is a separate `report.md` page.

`--max-tokens` remains a single-document detail-fallback option and conflicts with `--split`; use `--overview-max-tokens` for a split overview. Split output uses shared schema links, so `--inline-schemas` also conflicts with `--split`. Schemas and examples still require `--detail full` and their respective inclusion flags. Each reduced-detail page tells the reader how to retrieve fuller detail into another directory.

Generated paths combine a readable slug with a hash of the operation, service name or schema reference. They stay stable when filters or unrelated operations change. Regeneration uses `.vimanam-manifest.json` to track owned files: it removes obsolete generated pages only when their contents are unchanged, preserves unrelated files, and refuses to overwrite edited generated pages or unmanaged collisions. Move edited pages aside or use a fresh output directory before regenerating. Symlink output paths and parents are rejected; use their resolved paths (for example `/private/tmp` instead of `/tmp` on macOS). Keep the manifest alongside the generated tree.

## Agent-navigable Skill tree

```bash
vimanam openapi.json --output-mode skill -o ./api-skill \
  --detail full --include-schemas --include-examples --include-auth \
  --overview-max-tokens 1600
```

Start with `api-skill/SKILL.md`, which has YAML frontmatter (`name`, `description`, API `version`) and explains how to choose reads. Follow a service hub to select an endpoint by method/path, operation ID and short description, then follow that endpoint's schema links as needed. `endpoints/index.md` lists all selected operations; `schemas/index.md` lists reachable shared schemas; `services/index.md` lists service hubs. Multi-tag operations share one endpoint file. `api.md` retains global API guidance.

Hub entries show the approximate cost of each linked file, using characters/4 rounded up from its final emitted contents. An endpoint estimate covers that endpoint file alone; reading linked schemas incurs their separate costs. Estimates help the agent choose what to load without loading the full referenced graph first.

`--overview-max-tokens` affects only `SKILL.md` (default estimate: 1600). When service entries do not fit, the root links to the complete `index.md` map; all detail files and directory hubs stay unchanged. Essential navigation may exceed an extremely small budget. Existing filters, sorting, detail levels and schema/example flags apply to detail files. Reduced-detail pages include retrieval commands, and omitted documentation is distinguished from content absent in the source spec. Schema tables still require `--detail full --include-schemas`. This profile uses the same stable paths and ownership protections as split output and conflicts with `--split`, `--max-tokens`, `--stats` and `--inline-schemas`.

## Options

```
Usage: vimanam [OPTIONS] <FILE>
       vimanam <COMMAND>

Commands:
  completions  Generate shell completions and print them to stdout
  diff         Compare two versions of a spec and report what changed, classified as breaking, non-breaking or needing review
  help         Print this message or the help of the given subcommand(s)

Arguments:
  <FILE>  Path to the OpenAPI JSON or YAML file

Options:
  -o, --output <FILE>                      Output file path, or directory for split/skill output
      --split <SPLIT>                      Write linked Markdown pages to the --output directory [service, tag, endpoint]
      --output-mode <OUTPUT_MODE>          Write an agent-navigable SKILL.md tree to the --output directory [skill]
      --overview-max-tokens <N>            Token budget for the split index or SKILL.md only; detail pages stay at the requested level
      --method                             Group endpoints by HTTP method instead of by service
      --group-by <service|method|path>     Grouping method for endpoints [default: service]
      --flat                               Generate a flat list without hierarchical structure
      --service-filter <SERVICE[,...]>     Include only specific services (comma-separated)
      --path-filter <PATTERN>              Filter endpoints by path pattern
      --method-filter <METHOD[,...]>       Filter by HTTP methods (comma-separated)
      --operation <METHOD PATH>            Render exactly this operation, as "METHOD /path/template" (repeatable; exact path match, Swagger 2 basePath excluded)
      --operation-id <ID>                  Render exactly the operation(s) with this operationId (repeatable, case-sensitive)
      --exclude-deprecated                 Hide deprecated endpoints
      --required-only                      Only show required parameters
      --detail <summary|basic|standard|full> Control amount of information [default: summary]
      --include-schemas                    Include request/response schemas
      --inline-schemas                     Fully inline every $ref schema instead of linking to a shared "Schema Definitions" section
      --schema-depth <N>                   Maximum schema traversal edges from a root (0..=24). Zero retains only roots
      --schema <NAME>                      Read a named schema directly (repeatable); full metadata, independent of reachability
      --schema-field <NAME#POINTER>        Read a schema subtree and its ancestors (repeatable; JSON-pointer escaping)
      --include-examples                   Include request/response examples
      --include-auth                       Show authentication requirements
      --toc                                Include the table of contents (the default; when both are given, the later of --toc/--no-toc wins)
      --no-toc                             Skip table of contents
      --sort <alpha|path-length|none>      Sorting method [default: alpha]
      --max-tokens <N>                     Fit single-file output to a token budget, stepping detail down (full → summary). The hygiene report is outside the budget
      --no-report                          Skip the spec hygiene report appended after the documentation
      --stats                              Dry run: per-service endpoint counts and estimated tokens (chars/4). TOTAL is one whole-document render, not the sum of the rows
      --costs                              Dry run: token-cost analysis (chars/4) with per-endpoint and per-schema costs, reference amplification and hotspots. Rows overlap through shared schemas and the document frame
  -h, --help                               Print help
  -V, --version                            Print version
```

```
Compare two versions of a spec and report what changed, classified as breaking, non-breaking or needing review

Usage: vimanam diff [OPTIONS] <OLD> <NEW>

Arguments:
  <OLD>  The older spec (JSON or YAML)
  <NEW>  The newer spec (JSON or YAML)

Options:
      --report            Append a Deltas section: spec hygiene counts for both specs and the estimated token size of each at --detail full --include-schemas
      --format <FORMAT>   Output format: a Markdown report or machine-readable JSON with stable change IDs [default: markdown] [possible values: markdown, json]
      --fail-on-breaking  Exit with status 3 when any breaking change is found, after writing the full report
  -o, --output <FILE>     Write the diff to FILE instead of stdout
  -h, --help              Print help (see more with '--help')
```

## Preparing API context for LLMs

Large API specs are a poor fit for LLM context windows: a 3 MB swagger file is hundreds of thousands of tokens of JSON, most of it boilerplate. Vimanam's detail levels and filters act as a token-budget dial, letting you hand an LLM (or a coding agent) exactly the slice of the API it needs, as compact Markdown.

For an agent that should choose its own reads, write a Skill tree instead of one file. `SKILL.md` is a compact map; service hubs, endpoint pages, and schema pages stay on disk at the requested detail, each with an approximate read cost. `--split` is the same idea as ordinary linked pages. When one schema is still too large, read it with `--schema` or `--schema-field`, or bound expansion with `--schema-depth`. Those reads keep field types, requiredness, descriptions, and enums. Single-file `--max-tokens` still steps detail down and can drop schema tables, so use it for a bounded overview, not for field research.

```bash
# Agent entry point: compact SKILL.md, full detail on the linked pages
vimanam openapi.json --output-mode skill -o ./api-skill \
  --detail full --include-schemas --include-examples

# Linked pages with a small index and full endpoint files
vimanam openapi.json --split endpoint -o ./api-docs \
  --detail full --include-schemas --overview-max-tokens 2000

# One field, or a depth-bounded operation, when a schema page is still too big
vimanam openapi.json --schema-field 'Finding#/properties/tags'
vimanam openapi.json --operation-id GetFinding --detail full \
  --include-schemas --schema-depth 2 --no-report
```

```bash
# 20,000-ft view: a compact map of every service and operation name.
# Good as always-loaded context so the model knows what the API can do.
vimanam openapi.json --detail summary -o api-map.md

# Zoom into one service when the task touches it — parameters and responses
# included, everything else excluded
vimanam openapi.json --service-filter Findings --detail standard -o findings-api.md

# Slice by path or method instead
vimanam openapi.json --path-filter /v1/scans --detail standard -o scans-api.md
vimanam openapi.json --method-filter GET --detail basic -o read-api.md

# Or let Vimanam pick the detail level: ask for as much of a service as fits a
# token budget. It starts at the requested detail and steps down as needed,
# reporting any reduction on stderr.
vimanam openapi.json --service-filter Findings --detail full --max-tokens 8000 -o findings-api.md
```

`--max-tokens` uses a chars/4 token estimate. It is approximate, not a tokenizer count or an exact cap: if even summary output exceeds it, the summary is emitted with an over-budget notice. Explicit schema selections also remain intact when over budget. When the output is fed to a model, add `--no-report`: the endpoint hygiene report is appended outside the body budget.

### Selecting exact operations

When a task concerns one or two operations, `--operation` and `--operation-id` render exactly those and nothing else:

```bash
vimanam openapi.json --operation "GET /users" --detail full --include-schemas --flat
vimanam openapi.json --operation "GET /users" --operation "DELETE /users/{id}" --detail standard
vimanam openapi.json --operation-id listUsers --detail full
```

Unlike `--path-filter`, which is a substring match (`--path-filter /users` also picks up `/users/{id}`, `/users/{id}/keys` and `/admin/users`), `--operation "<METHOD> <PATH>"` matches the spec's path template byte for byte: the method is case-insensitive, but `{id}` and `{userId}` are different templates and `/users/` is not `/users`. For Swagger 2 specs the path is the key under `paths`, without `basePath`. `--operation-id` matches operation IDs exactly (case-sensitive); if the spec reuses an ID, every operation carrying it is selected. Both flags are repeatable and can be combined — the selection is their union — and they are ANDed with the other filters, so `--operation "GET /users" --exclude-deprecated` renders nothing if that operation is deprecated (with a warning on stderr naming the filter). A value that matches no operation in the spec is an error (exit 1) rather than an empty document.

Under the default service grouping, services with no selected operation are left out, and an operation with several tags appears once under each of its services; add `--flat` to get each selected operation exactly once. The selection also scopes `--stats`, `--max-tokens` and the hygiene report.

The selectors round-trip with [`diff --format json`](#json-output): every change record's `endpoint.method` and `endpoint.path` can be passed back as `--operation "<method> <path>"` to render that operation's contract from the old spec (for `endpoint_removed`) or the new one:

```bash
vimanam diff v1.json v2.json --format json \
  | jq -r '.changes[] | select(.kind != "endpoint_removed") | "\(.endpoint.method) \(.endpoint.path)"' \
  | sort -u | while read -r op; do vimanam v2.json --operation "$op" --detail full --include-schemas --flat --no-report; done
```

Before choosing which slice to generate, `--stats` sizes the candidates without writing any Markdown: it prints one row per service with its visible endpoint count and the estimated token size of rendering that service alone, at whatever `--detail`, grouping and filter flags you pass, plus a TOTAL row for the whole document. A service left with no visible endpoint (for example, one whose only operations are dropped by `--exclude-deprecated`) is omitted from the table.

```bash
$ vimanam openapi.json --stats --detail standard
SERVICE    ENDPOINTS   ~TOKENS
Findings          42      6231
Scans             18      2890
Projects          31      4410
TOTAL             91     13102
```

Read it as a menu: a service that fits your budget can be pulled with `--service-filter <name>` at that detail level; one that does not can be re-measured at a lower `--detail` or handed to `--max-tokens`. Like `--max-tokens`, `~TOKENS` is a chars/4 estimate of the documentation body (the hygiene report is excluded). Each row is a separate render of that service alone, while `TOTAL` is a single render of the whole filtered document, so the rows do not necessarily add up to it: an operation tagged with several services is counted in each of their rows but once in the total, and the preamble and shared schema definitions are counted once per row.

A workflow that works well with coding agents: generate the `--detail summary` map once and reference it from the project's agent instructions (e.g. `CLAUDE.md`); have the agent regenerate a `--service-filter ... --detail standard` slice on demand when a task involves specific endpoints.

Output is deterministic — the same spec and flags produce byte-identical Markdown — so generated context files diff cleanly in git and don't needlessly invalidate LLM prompt caches.

## Comparing spec versions

`vimanam diff <OLD> <NEW>` compares two versions of a spec and prints a Markdown report of what changed, so a spec update can be reviewed (or gated in CI) before clients find out the hard way.

```bash
vimanam diff v1/openapi.json v2/openapi.json --report --fail-on-breaking
```

```markdown
# API Diff: Widgets API 1.0.0 → 1.1.0

**Summary:** 1 endpoint added, 1 removed, 4 changed; 4 breaking, 8 non-breaking, 1 to review

## Breaking changes (4)

| Change | Endpoint | Detail |
|--------|----------|--------|
| Parameter newly required | `GET /widgets/{id}` | `fields` (query) |
| Response removed | `GET /widgets/{id}` | 404 |
| operationId changed | `DELETE /widgets/{id}` | `Widgets_DeleteWidget` → `Widgets_RemoveWidget` |
| Endpoint removed | `GET /legacy` | was deprecated |

## Non-breaking changes (8)

| Change | Endpoint | Detail |
|--------|----------|--------|
| Response schema changed | `GET /widgets` | 200 `/properties/pricing` added |
| Response schema changed | `GET /widgets` | 200 `/properties/pricing` added to `required` |
| ... | | |

## Needs review (1)

| Change | Endpoint | Detail |
|--------|----------|--------|
| Request schema changed | `POST /widgets` | `/properties/weight/format` changed `float` → `double` |
```

**What is compared.** Endpoints are matched by method and path, parameters by name and location, responses by status code. Request and response bodies are compared as *resolved* schemas: every `$ref` is inlined first, so a change to a shared component schema shows up on every endpoint that references it — even when the operation object itself is byte-identical, which is the case a path-level or operation-level diff misses. Field-level differences are reported as JSON pointers into the resolved schema (`/properties/pricing`, `/items/type`). Descriptions, titles, examples and `x-*` extensions are ignored, and the order of `required` and `enum` members is irrelevant.

**Severity.** Every change is classified from the point of view of an existing client:

| Severity | Examples |
|----------|----------|
| Breaking | endpoint or response code removed; parameter removed, newly required or moved; `operationId` changed; a `type` changed anywhere; request: property removed, property added to `required`, `enum` member removed, `additionalProperties` set to `false`, `nullable` set to `false` (clients sending `null` now fail); response: schema removed entirely, property removed, property removed from `required`, `enum` member removed, `nullable` set to `true` |
| Non-breaking | endpoint or response code added; optional parameter added; parameter made optional; `deprecated` toggled; `operationId` added; request: property added, `required` member removed, `enum` member added, `nullable` set to `true`; response: property added, `required` member added, `nullable` set to `false` |
| Needs review | a response `enum` gaining a member (strictly typed clients reject values they do not know); anything else in a schema — `format`, `minimum`/`maximum`, `pattern`, `items` shape, `allOf`/`oneOf`/`anyOf` variants, other `additionalProperties` changes. Never trips `--fail-on-breaking` |

When a property is removed, the accompanying "removed from `required`" row for that property is not reported — the property going away is the change.

**Exit codes.** `0` — no breaking changes (or `--fail-on-breaking` not given); `1` — a spec failed to parse or the output could not be written; `2` — usage error; `3` — breaking changes found and `--fail-on-breaking` was given. The full report is always written before exiting.

`--report` appends a `## Deltas` section with the spec hygiene counts for both versions and the estimated token size of each at `--detail full --include-schemas`, so a spec update's documentation cost is visible alongside its API changes. `-o FILE` writes the report to a file (the subcommand has its own `-o`; the conversion flags do not apply to `diff`).

**Known limitations.** Only the first media type of a request body or response is compared. A path-template rename (`/pets/{id}` → `/pets/{petId}`) appears as a removal plus an addition. `allOf`/`oneOf`/`anyOf` lists are compared index-wise, so reordering variants is reported as changes. The model drops `null` from OpenAPI 3.1 type arrays (`type: ["string", "null"]` is folded to `string`), so a nullability change expressed as a type array is not detected — only `nullable: true`/`false` is.

### JSON output

`--format json` prints the same comparison as a single pretty-printed JSON document (2-space indentation, trailing newline) on stdout or in `-o FILE`, for CI tools, review bots and coding agents. Exit codes are identical across formats, and under `--fail-on-breaking` the complete document is written and flushed before exit status 3. Diagnostics go to stderr only, so stdout is always one parseable document. `-o FILE` and stdout carry byte-identical bytes.

```bash
vimanam diff old.json new.json --format json --report -o diff.json --fail-on-breaking
```

```json
{
  "schema_version": 1,
  "generator": { "name": "vimanam", "version": "1.6.0" },
  "old": { "title": "Widgets API", "version": "1.0.0", "file_sha256": "<64 lowercase hex>" },
  "new": { "title": "Widgets API", "version": "1.1.0", "file_sha256": "<64 lowercase hex>" },
  "summary": {
    "endpoints_added": 1, "endpoints_removed": 1, "endpoints_changed": 4,
    "breaking": 4, "non_breaking": 8, "review": 1
  },
  "changes": [
    {
      "id": "vc1_<64 lowercase hex>",
      "endpoint": { "method": "GET", "path": "/widgets/{id}" },
      "kind": "parameter_required_changed",
      "severity": "breaking",
      "details": { "name": "fields", "location": "query", "now_required": true }
    },
    {
      "id": "vc1_…",
      "endpoint": { "method": "GET", "path": "/widgets" },
      "kind": "response_schema_changed",
      "severity": "non_breaking",
      "details": {
        "status": "200",
        "schema_change": {
          "pointer": "/properties/pricing",
          "target": "property",
          "member": "pricing",
          "operation": "added",
          "before": { "present": false },
          "after": { "present": true, "value": { "type": "number" } }
        }
      }
    }
  ],
  "deltas": { "hygiene": [ { "check": "Missing description", "old": 3, "new": 1 } ],
              "tokens": { "old": 12345, "new": 12890, "estimate": "chars/4", "detail": "full+schemas" } }
}
```

**Field reference.** `schema_version` is `1`; bumping it (or the `vc1_` ID prefix below) signals a contract change. `generator.version` is the vimanam version that produced the document. `old`/`new` carry each spec's title, version and `file_sha256`. `summary` mirrors the Markdown summary line (its counts always agree); `changes` lists every change in the order the Markdown tables use before grouping by severity; `severity` is one of `breaking`, `non_breaking`, `review`. `deltas` is present **only** with `--report` — the key is omitted, never `null` — with hygiene rows in the same order as the Markdown `## Deltas` table and the same chars/4 token estimate the `--report` Markdown shows. Every `kind` maps one-to-one to an internal `ChangeKind` variant, and its `details` object carries exactly that variant's fields (e.g. `parameter_required_changed` → `name`, `location`, `now_required`; `response_schema_changed` → `status`, `schema_change`; `operation_id_changed` → `old`/`new`, which may be `null` because a spec may lack an `operationId`). `status` stays a string (`"200"`, `"default"`, `"4XX"`).

**Presence encoding.** `before`/`after` are each either `{ "present": false }` or `{ "present": true, "value": <json> }`. A present `value` may legitimately be JSON `null` (`default: null`, a `null` member of `enum`) — that is distinct from absence. One special case: "no schema at all" is encoded internally as a null at the root pointer `""`, so a root-level `null` side is reported as `{ "present": false }` with `operation` flipped to `added`/`removed` (a body appeared or disappeared) rather than a change to/from `null`.

**Pointers.** `schema_change.pointer` is an RFC 6901 pointer exactly as the differ emits it, and `target`/`member` classify it according to the schema grammar: `target` is one of `type`, `required_member`, `enum_member`, `property`, `additional_properties`, `nullable`, `other`, and `member` is the decoded last pointer segment for the targets that address a named member (`null` otherwise). A property *named* `type` is a `property`, not the `type` keyword. Pointers address the **resolved, canonicalised schema** — `$ref`s are inlined and `description`/`title`/`example(s)`/`deprecated`/`x-*` annotations are stripped before comparing — so a pointer is a location in that normalised form, not necessarily in your input file. `required`/`enum` members appear as `/required/<name>` and `/enum/<value>`.

**Change identity.** Every record's `id` is `"vc1_" + SHA-256` (lowercase hex) of a canonical JSON object `{ "v": 1, "endpoint": …, "kind": …, "details": … }` — the same `endpoint`, `kind` and `details` values the record emits, serialised with object keys sorted recursively and no whitespace. Severity, display strings, timestamps, file paths, `file_sha256` and the generator version are deliberately **not** part of the hash, so a future change to the severity rules cannot rewrite any ID; the `vc1_` prefix and the `"v": 1` field version the construction and must move together if it ever changes. Consequences you can rely on:

- the same pair of specs yields identical IDs on every run;
- reformatting a spec (whitespace, key order) changes `file_sha256` but no ID or record;
- an unrelated edit elsewhere in the spec leaves existing IDs unchanged;
- two different changes at the same pointer (`string` → `integer` vs `string` → `boolean`) get different IDs;
- a change behind a shared `$ref` produces one record per affected endpoint, each with its own ID, because the endpoint is part of the hash input;
- IDs are unique within a document.

**Known limits for IDs.** `allOf`/`oneOf`/`anyOf` members are compared by index, so reordering them changes pointers and therefore IDs, even when the set of variants is unchanged. A path-template rename is a removal plus an addition with two fresh IDs — there is no rename detection.

## Continuous integration

Generate your API docs in CI with the [vimanam GitHub Action](https://github.com/noemaforge/vimanam-action) — it downloads the matching prebuilt binary (no Rust toolchain on the runner), verifies its SHA256 checksum, and runs vimanam:

```yaml
- uses: noemaforge/vimanam-action@4599a14c84d9d7bce1ec34ed9f12f3036f06b518 # v1
  with:
    version: v1.6.0
    spec: openapi.json
    output: docs/api-map.md
    detail: summary

# Output is deterministic, so this fails CI when the committed docs drift from the spec:
- run: git diff --exit-code -- docs/api-map.md
```

To block a pull request that breaks API clients, diff the proposed spec against the one on the default branch; exit status 3 means breaking changes were found (see [Comparing spec versions](#comparing-spec-versions)):

```yaml
- run: git fetch --no-tags --depth=1 origin main:refs/remotes/origin/main
- run: git show origin/main:openapi.json > /tmp/openapi-main.json
- run: vimanam diff /tmp/openapi-main.json openapi.json --report --fail-on-breaking -o api-diff.md
```

Write the report with `-o` rather than piping it through `tee`: a pipeline would return `tee`'s exit status, not vimanam's, unless the shell runs with `pipefail`. `api-diff.md` is still written when the step fails, so a later step (`if: always()`) can upload it or post it as a pull-request comment.

Pin the action to a commit SHA, not a mutable tag — see the action's [Pinning](https://github.com/noemaforge/vimanam-action#pinning) notes. More patterns in [`examples/`](https://github.com/noemaforge/vimanam-action/tree/main/examples).

Set `version` explicitly to choose the Vimanam release; pinning the action itself does not select the latest binary.

## Supported OpenAPI Versions

Vimanam supports:

- OpenAPI 2.0 (Swagger) documents using the `swagger` field
- OpenAPI 3.0 documents using the `openapi` field
- OpenAPI 3.1 documents using the supported schema fields, including type arrays; this is not a full JSON Schema implementation. See the [diff limitations](#comparing-spec-versions) for type-array nullability handling.

Reference resolution supports internal `#/...` references. References to other files or URLs are not loaded; bundle those specs before using Vimanam.

## Output Examples

The generated documentation includes:

### Server and Authentication Information
```markdown
## Server URLs
* https://api.example.com/v1
* https://dev-api.example.com/v1

## Authentication
* **apiKeyAuth**: API Key authentication (apiKey)
* **oauth2**: OAuth 2.0 authorization (oauth2)
```

### Endpoint Documentation
```markdown
### createUser {#createuser}
**Operation:** POST /users

**Description:** Create a new user account
**Operation ID:** `createUser`

#### Parameters
| Name | In | Required | Description |
|------|----|---------:|-------------|
| `body` | body | Yes | User information |

#### Responses
| Code | Type | Description |
|------|------|-------------|
| 201 | application/json | User created successfully |
| 400 | application/json | Invalid request |
```

## License

Apache License 2.0