Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ coverage.html
dist/

# Symlinked from AGENTS.md by mise install
CLAUDE.md
CLAUDE.md

go.work.sum
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,24 @@ go install github.com/speakeasy-api/openapi/cmd/openapi@latest

The CLI provides three main command groups:

- **`openapi spec`** - Commands for working with OpenAPI specifications ([documentation](./openapi/cmd/README.md))
- **`openapi spec`** - Commands for working with OpenAPI specifications ([documentation](./cmd/openapi/commands/openapi/README.md))
- `bootstrap` - Create a new OpenAPI document with best practice examples
- `bundle` - Bundle external references into components section
- `clean` - Remove unused components from an OpenAPI specification
- `explore` - Interactively explore an OpenAPI specification in the terminal
- `inline` - Inline all references in an OpenAPI specification
- `join` - Join multiple OpenAPI documents into a single document
- `localize` - Localize an OpenAPI specification by copying external references to a target directory
- `optimize` - Optimize an OpenAPI specification by deduplicating inline schemas
- `sanitize` - Remove unwanted elements from an OpenAPI specification
- `snip` - Remove selected operations from an OpenAPI specification (interactive or CLI)
- `upgrade` - Upgrade an OpenAPI specification to the latest supported version
- `validate` - Validate an OpenAPI specification document

- **`openapi arazzo`** - Commands for working with Arazzo workflow documents ([documentation](./arazzo/cmd/README.md))
- **`openapi arazzo`** - Commands for working with Arazzo workflow documents ([documentation](./cmd/openapi/commands/arazzo/README.md))
- `validate` - Validate an Arazzo workflow document

- **`openapi overlay`** - Commands for working with OpenAPI overlays ([documentation](./overlay/cmd/README.md))
- **`openapi overlay`** - Commands for working with OpenAPI overlays ([documentation](./cmd/openapi/commands/overlay/README.md))
- `apply` - Apply an overlay to an OpenAPI specification
- `compare` - Compare two specifications and generate an overlay describing differences
- `validate` - Validate an OpenAPI overlay document
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package arazzo

import "github.com/spf13/cobra"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package arazzo

import (
"context"
Expand Down
182 changes: 182 additions & 0 deletions openapi/cmd/README.md → cmd/openapi/commands/openapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ OpenAPI specifications define REST APIs in a standard format. These commands hel
- [`optimize`](#optimize)
- [`bootstrap`](#bootstrap)
- [`localize`](#localize)
- [`explore`](#explore)
- [`snip`](#snip)
- [Common Options](#common-options)
- [Output Formats](#output-formats)
- [Examples](#examples)
Expand Down Expand Up @@ -730,6 +732,186 @@ Address:
- You want to simplify file management for complex multi-file specifications
- You're creating documentation packages that include all referenced files

### `explore`

Interactively explore an OpenAPI specification in a terminal user interface.

```bash
# Launch the explorer
openapi spec explore ./spec.yaml

# Get help on keyboard shortcuts
# (Press '?' in the explorer)
```

What the explorer provides:

- **Interactive navigation** - Browse all API operations with vim-style keyboard shortcuts
- **Operation details** - View parameters, request bodies, responses, and more
- **Color-coded methods** - Visual differentiation by HTTP method (GET=green, POST=blue, etc.)
- **Fold/unfold details** - Toggle detailed information with Space or Enter
- **Search through operations** - Quickly find endpoints in large specifications
- **Help modal** - Built-in keyboard shortcut reference

**Keyboard Navigation:**

| Key | Action |
| ----------------- | -------------------------- |
| `↑` / `k` | Move up |
| `↓` / `j` | Move down |
| `gg` | Jump to top |
| `G` | Jump to bottom |
| `Ctrl-U` | Scroll up by half screen |
| `Ctrl-D` | Scroll down by half screen |
| `Enter` / `Space` | Toggle operation details |
| `?` | Show/hide help |
| `q` / `Esc` | Quit |

**What you can view:**

- Operation ID, summary, and description
- HTTP method and path
- Parameters (name, location, required status, description)
- Request body content types
- Response status codes and descriptions
- Tags and deprecation warnings

**Benefits:**

- **Faster understanding** - Quickly grasp API structure without parsing YAML/JSON
- **Better navigation** - Jump between operations more efficiently than text editors
- **Visual clarity** - Color-coding and formatting make operations easier to distinguish
- **No tool installation** - Works in any terminal, no browser required
- **Offline friendly** - Explore specifications without network access

**Use Explore when:**

- You need to understand a new or unfamiliar API specification
- You want to quickly review endpoints and their parameters
- You're debugging API structure or looking for specific operations
- You prefer terminal-based workflows over web-based viewers
- You need to present or demo API operations in a meeting

### `snip`

Remove selected operations from an OpenAPI specification and automatically clean up unused components.

```bash
# Interactive mode - browse and select operations via TUI
openapi spec snip ./spec.yaml
openapi spec snip ./spec.yaml ./filtered-spec.yaml

# CLI mode - remove by operation ID
openapi spec snip --operationId deleteUser --operationId adminDebug ./spec.yaml

# CLI mode - remove by operation ID (comma-separated)
openapi spec snip --operationId deleteUser,adminDebug ./spec.yaml

# CLI mode - remove by path:method
openapi spec snip --operation /users/{id}:DELETE --operation /admin:GET ./spec.yaml

# CLI mode - remove by path:method (comma-separated)
openapi spec snip --operation /users/{id}:DELETE,/admin:GET ./spec.yaml

# CLI mode - mixed approaches
openapi spec snip --operationId deleteUser --operation /admin:GET ./spec.yaml

# Write in-place (CLI mode only)
openapi spec snip -w --operation /internal/debug:GET ./spec.yaml
```

**Two Operation Modes:**

**Interactive Mode** (no operation flags):
- Launch a terminal UI to browse all operations
- Select operations with Space key
- Press 'a' to select all, 'A' to deselect all
- Press 'w' to write the result (prompts for file path)
- Press 'q' or Esc to cancel

**Command-Line Mode** (operation flags specified):
- Remove operations specified via flags without UI
- Supports `--operationId` for operation IDs
- Supports `--operation` for path:method pairs
- Both flags support comma-separated values or multiple flags

**What snip does:**

1. Removes the specified operations from the document
2. Removes path items that become empty after operation removal
3. Automatically runs Clean() to remove unused components
4. Preserves all other operations and valid references

**Before snipping:**

```yaml
paths:
/users:
get:
operationId: getUsers
responses:
'200':
$ref: '#/components/responses/UserResponse'
delete:
operationId: deleteAllUsers
responses:
'204':
description: No content
/admin/debug:
get:
operationId: debugInfo
responses:
'200':
description: Debug info
components:
schemas:
User:
type: object
UnusedSchema:
type: object
responses:
UserResponse:
description: User response
```

**After snipping** (removed deleteAllUsers and debugInfo):

```yaml
paths:
/users:
get:
operationId: getUsers
responses:
'200':
$ref: '#/components/responses/UserResponse'
components:
schemas:
User:
type: object
responses:
UserResponse:
description: User response
# DELETE operation removed, /admin/debug path removed entirely
# UnusedSchema cleaned up automatically
```

**Benefits of snipping:**

- **Reduce API surface**: Remove deprecated or internal operations before publishing
- **Create filtered specs**: Generate subsets of your API for specific clients or use cases
- **Interactive selection**: Visual browser makes it easy to identify and select operations
- **Automatic cleanup**: Unused components are removed automatically
- **Flexible input**: Support both operation IDs and path:method pairs
- **Batch processing**: Remove multiple operations in one command

**Use Snip when:**

- You need to remove deprecated operations from a specification
- You want to create a filtered version of your API for specific clients
- You're preparing a public API specification and need to remove internal endpoints
- You want to reduce the size and complexity of a specification
- You need to create different API variants from a single source

## Common Options

All commands support these common options:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package openapi

import (
"context"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package openapi

import (
"context"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package openapi

import (
"context"
Expand Down Expand Up @@ -53,9 +53,7 @@ Output options:
Run: runClean,
}

var (
cleanWriteInPlace bool
)
var cleanWriteInPlace bool

func init() {
cleanCmd.Flags().BoolVarP(&cleanWriteInPlace, "write", "w", false, "write result in-place to input file")
Expand Down
121 changes: 121 additions & 0 deletions cmd/openapi/commands/openapi/explore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package openapi

import (
"context"
"fmt"
"os"
"path/filepath"

tea "github.com/charmbracelet/bubbletea"
"github.com/speakeasy-api/openapi/cmd/openapi/internal/explore"
"github.com/speakeasy-api/openapi/cmd/openapi/internal/explore/tui"
"github.com/speakeasy-api/openapi/openapi"
"github.com/spf13/cobra"
)

var exploreCmd = &cobra.Command{
Use: "explore <file>",
Short: "Interactively explore an OpenAPI specification",
Long: `Launch an interactive terminal UI to browse and explore OpenAPI operations.

This command provides a user-friendly interface for navigating through API
endpoints, viewing operation details, parameters, request/response information,
and more.

Navigation:
↑/k Move up
↓/j Move down
gg Jump to top
G Jump to bottom
Ctrl-U Scroll up by half a screen
Ctrl-D Scroll down by half a screen
Enter/Space Toggle operation details
? Show help
q/Esc Quit

The explore command helps you understand API structure and operation details
without needing to manually parse the OpenAPI specification file.`,
Args: cobra.ExactArgs(1),
RunE: runExplore,
}

func runExplore(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
inputFile := args[0]

// Load the OpenAPI document
doc, err := loadOpenAPIDocument(ctx, inputFile)
if err != nil {
return err
}

// Collect operations from the document
operations, err := explore.CollectOperations(ctx, doc)
if err != nil {
return fmt.Errorf("failed to collect operations: %w", err)
}

if len(operations) == 0 {
return fmt.Errorf("no operations found in the OpenAPI document")
}

// Get document info for display
docTitle := doc.Info.Title
if docTitle == "" {
docTitle = "OpenAPI"
}
docVersion := doc.Info.Version
if docVersion == "" {
docVersion = "unknown"
}

// Create and run the TUI
m := tui.NewModel(operations, docTitle, docVersion)
p := tea.NewProgram(m, tea.WithAltScreen())

if _, err := p.Run(); err != nil {
return fmt.Errorf("error running explorer: %w", err)
}

return nil
}

// loadOpenAPIDocument loads an OpenAPI document from a file
func loadOpenAPIDocument(ctx context.Context, file string) (*openapi.OpenAPI, error) {
cleanFile := filepath.Clean(file)

f, err := os.Open(cleanFile)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()

doc, validationErrors, err := openapi.Unmarshal(ctx, f)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal OpenAPI document: %w", err)
}
if doc == nil {
return nil, fmt.Errorf("failed to parse OpenAPI document: document is nil")
}

// Report validation errors as warnings but continue
if len(validationErrors) > 0 {
fmt.Fprintf(os.Stderr, "⚠️ Found %d validation errors in document:\n", len(validationErrors))
for i, validationErr := range validationErrors {
if i < 5 { // Limit to first 5 errors
fmt.Fprintf(os.Stderr, " %d. %s\n", i+1, validationErr.Error())
}
}
if len(validationErrors) > 5 {
fmt.Fprintf(os.Stderr, " ... and %d more\n", len(validationErrors)-5)
}
fmt.Fprintln(os.Stderr)
}

return doc, nil
}

// GetExploreCommand returns the explore command for external use
func GetExploreCommand() *cobra.Command {
return exploreCmd
}
Loading