> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/wevm/incur/llms.txt
> Use this file to discover all available pages before exploring further.

# Middleware

> Middleware system for cross-cutting concerns

## middleware()

Creates a strictly typed middleware handler. Pass the vars and env schemas as generics for full type inference.

### Signature

```typescript theme={null}
function middleware<
  const vars extends z.ZodObject<any> | undefined = undefined,
  const env extends z.ZodObject<any> | undefined = undefined,
>(handler: Handler<vars, env>): Handler<vars, env>
```

### Parameters

<ParamField path="handler" type="Handler<vars, env>" required>
  The middleware handler function that receives context and next function.
</ParamField>

### Return Type

<ResponseField name="Handler" type="Handler<vars, env>">
  Returns the same handler with proper type inference. This is an identity function that exists purely for type safety.
</ResponseField>

### Examples

#### Basic Middleware

```typescript theme={null}
import { middleware } from 'incur'

const logger = middleware(async (c, next) => {
  console.log(`[${c.command}] Starting...`)
  await next()
  console.log(`[${c.command}] Done`)
})

cli.use(logger)
```

#### Typed Middleware

Use type parameters for full type inference with vars and env:

```typescript theme={null}
import { Cli, middleware, z } from 'incur'

const cli = Cli.create('my-cli', {
  vars: z.object({
    userId: z.string().default('anonymous'),
    requestId: z.string().default('default-id')
  }),
  env: z.object({
    API_TOKEN: z.string()
  })
})

// Pass typeof to get full type inference
const auth = middleware<typeof cli.vars, typeof cli.env>(
  async (c, next) => {
    // c.env.API_TOKEN is typed as string
    // c.set() and c.var are fully typed
    c.set('userId', 'user-123')
    c.set('requestId', crypto.randomUUID())
    await next()
  }
)

cli.use(auth)
```

## Handler Type

The middleware handler function type.

### Type Signature

```typescript theme={null}
type Handler<
  vars extends z.ZodObject<any> | undefined = undefined,
  env extends z.ZodObject<any> | undefined = undefined,
> = (
  context: Context<vars, env>,
  next: () => Promise<void>
) => Promise<void> | void
```

### Parameters

<ParamField path="context" type="Context<vars, env>">
  The middleware context object. See [Context Type](#context-type) below.
</ParamField>

<ParamField path="next" type="() => Promise<void>">
  Function to call the next middleware in the chain or the command handler. Calling `next()` is optional - omitting it short-circuits the chain.
</ParamField>

## Context Type

The context object passed to middleware handlers. Contains CLI metadata, environment variables, and variable management.

### Properties

<ResponseField name="agent" type="boolean">
  Whether the consumer is an agent (stdout is not a TTY).

  ```typescript theme={null}
  middleware(async (c, next) => {
    if (!c.agent) {
      console.log('Running for human user')
    }
    await next()
  })
  ```
</ResponseField>

<ResponseField name="command" type="string">
  The resolved command path (e.g. `'deploy'` or `'pr create'`).

  ```typescript theme={null}
  middleware(async (c, next) => {
    console.log(`Executing: ${c.command}`)
    await next()
  })
  ```
</ResponseField>

<ResponseField name="name" type="string">
  The CLI name.
</ResponseField>

<ResponseField name="env" type="InferEnv<env>">
  Parsed environment variables from the CLI-level env schema. Fully typed based on the schema.

  ```typescript theme={null}
  const cli = Cli.create('my-cli', {
    env: z.object({
      API_TOKEN: z.string(),
      API_URL: z.string().default('https://api.example.com')
    })
  })

  const auth = middleware<typeof cli.vars, typeof cli.env>(
    async (c, next) => {
      // c.env.API_TOKEN is string
      // c.env.API_URL is string with default
      const client = new ApiClient(c.env.API_TOKEN, c.env.API_URL)
      await next()
    }
  )
  ```
</ResponseField>

<ResponseField name="var" type="InferVars<vars>">
  Variables set by upstream middleware. Read-only. Fully typed based on the vars schema.

  ```typescript theme={null}
  middleware(async (c, next) => {
    console.log(`User: ${c.var.userId}`)
    await next()
  })
  ```
</ResponseField>

<ResponseField name="version" type="string | undefined">
  The CLI version string, if provided.
</ResponseField>

<ResponseField name="set" type="<key extends keyof InferVars<vars>>(key: key, value: InferVars<vars>[key]) => void">
  Set a typed variable for downstream middleware and command handlers. Variables must be declared in the CLI's vars schema.

  ```typescript theme={null}
  const cli = Cli.create('my-cli', {
    vars: z.object({
      userId: z.string().default('anonymous'),
      timestamp: z.number().default(0)
    })
  })

  cli.use(middleware<typeof cli.vars>(async (c, next) => {
    c.set('userId', 'user-123')  // Typed!
    c.set('timestamp', Date.now())  // Typed!
    await next()
  }))
  ```
</ResponseField>

<ResponseField name="error" type="(options: ErrorOptions) => never">
  Return an error result, short-circuiting the middleware chain. The command handler will not run.

  ```typescript theme={null}
  middleware(async (c, next) => {
    if (!isAuthenticated) {
      return c.error({
        code: 'NOT_AUTHENTICATED',
        message: 'Please login first',
        retryable: false,
        cta: {
          description: 'Run:',
          commands: ['my-cli login']
        }
      })
    }
    await next()
  })
  ```

  <Expandable title="ErrorOptions">
    <ParamField path="code" type="string" required>
      Error code (e.g. `'NOT_AUTHENTICATED'`, `'RATE_LIMIT'`).
    </ParamField>

    <ParamField path="message" type="string" required>
      Human-readable error message.
    </ParamField>

    <ParamField path="retryable" type="boolean">
      Whether the operation can be retried. Helps agents decide if they should retry.
    </ParamField>

    <ParamField path="cta" type="CtaBlock">
      Call-to-action suggestions for the user.

      <Expandable title="CtaBlock">
        <ParamField path="description" type="string">
          Human-readable label. Defaults to `"Suggested commands:"`.
        </ParamField>

        <ParamField path="commands" type="Array<string | { command: string; description?: string }>">
          Commands to suggest to the user.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ResponseField>

## next() Function

The `next()` function continues the middleware chain or invokes the command handler if this is the last middleware.

### Behavior

* **Call `await next()`** to continue execution
* **Omit `await next()`** to short-circuit (useful for auth checks, caching, etc.)
* **Code before `next()`** runs before the command
* **Code after `next()`** runs after the command (cleanup, logging, etc.)

### Examples

#### Before and After Hooks

```typescript theme={null}
middleware(async (c, next) => {
  const start = Date.now()
  console.log(`Starting ${c.command}`)
  
  await next()  // Command runs here
  
  const duration = Date.now() - start
  console.log(`Finished ${c.command} in ${duration}ms`)
})
```

#### Short-Circuit on Error

```typescript theme={null}
middleware(async (c, next) => {
  if (!isValid(c.env.API_TOKEN)) {
    // Don't call next() - stop here
    return c.error({
      code: 'INVALID_TOKEN',
      message: 'API token is invalid'
    })
  }
  
  await next()
})
```

## Middleware Patterns

### Authentication

Check authentication before running commands:

```typescript theme={null}
import { Cli, middleware, z } from 'incur'

const cli = Cli.create('api', {
  vars: z.object({
    userId: z.string().default('anonymous')
  }),
  env: z.object({
    API_TOKEN: z.string()
  })
})

const auth = middleware<typeof cli.vars, typeof cli.env>(
  async (c, next) => {
    // Validate token
    const userId = await validateToken(c.env.API_TOKEN)
    
    if (!userId) {
      return c.error({
        code: 'INVALID_TOKEN',
        message: 'Authentication failed',
        cta: {
          description: 'Get a token:',
          commands: ['api login']
        }
      })
    }
    
    c.set('userId', userId)
    await next()
  }
)

cli.use(auth)
```

### Request Logging

Log all commands with timing:

```typescript theme={null}
const logger = middleware(async (c, next) => {
  const start = Date.now()
  console.log(`→ ${c.command}`)
  
  try {
    await next()
    console.log(`✓ ${c.command} (${Date.now() - start}ms)`)
  } catch (error) {
    console.log(`✗ ${c.command} (${Date.now() - start}ms)`)
    throw error
  }
})

cli.use(logger)
```

### Request ID Tracking

Add a unique ID to each request:

```typescript theme={null}
import { randomUUID } from 'crypto'

const cli = Cli.create('my-cli', {
  vars: z.object({
    requestId: z.string().default('default-id')
  })
})

const tracking = middleware<typeof cli.vars>(async (c, next) => {
  c.set('requestId', randomUUID())
  await next()
})

cli.use(tracking)

// Access in commands:
cli.command('deploy', {
  run(c) {
    console.log(`Request ID: ${c.var.requestId}`)
    return { requestId: c.var.requestId }
  }
})
```

### Rate Limiting

Implement rate limiting:

```typescript theme={null}
const rateLimiter = middleware(async (c, next) => {
  const remaining = await checkRateLimit(c.var.userId)
  
  if (remaining <= 0) {
    return c.error({
      code: 'RATE_LIMIT_EXCEEDED',
      message: 'Too many requests. Please try again later.',
      retryable: true
    })
  }
  
  await next()
})

cli.use(rateLimiter)
```

### Scoped Middleware

Middleware on command groups only runs for that group's commands:

```typescript theme={null}
const cli = Cli.create('my-cli')

// Root middleware - runs for all commands
cli.use(async (c, next) => {
  console.log('Root middleware')
  await next()
})

// Create a group with its own middleware
const admin = Cli.create('admin', {
  description: 'Admin commands'
})

// This middleware only runs for admin commands
admin.use(async (c, next) => {
  console.log('Admin middleware')
  // Check if user is admin
  if (!c.var.isAdmin) {
    return c.error({
      code: 'FORBIDDEN',
      message: 'Admin access required'
    })
  }
  await next()
})

admin.command('reset', {
  run() {
    return { reset: true }
  }
})

cli.command(admin)

// When running "my-cli admin reset":
// 1. Root middleware runs
// 2. Admin middleware runs
// 3. Command runs
```

### Conditional Middleware

Skip middleware based on conditions:

```typescript theme={null}
const cache = middleware(async (c, next) => {
  // Skip caching for agents
  if (c.agent) {
    await next()
    return
  }
  
  // Check cache for humans
  const cached = getFromCache(c.command)
  if (cached) {
    console.log('Using cached result')
    return  // Don't call next()
  }
  
  await next()
})

cli.use(cache)
```

### Error Recovery

Handle errors in middleware:

```typescript theme={null}
const errorHandler = middleware(async (c, next) => {
  try {
    await next()
  } catch (error) {
    // Log error details
    console.error(`Error in ${c.command}:`, error)
    
    // Re-throw to let the CLI handle it
    throw error
  }
})

cli.use(errorHandler)
```

## Middleware Order

Middleware runs in the order it's registered:

```typescript theme={null}
cli
  .use(async (c, next) => {
    console.log('1: before')
    await next()
    console.log('1: after')
  })
  .use(async (c, next) => {
    console.log('2: before')
    await next()
    console.log('2: after')
  })

// Output:
// 1: before
// 2: before
// [command runs]
// 2: after
// 1: after
```

### Middleware Chain Levels

Middleware runs in this order:

1. **CLI-level middleware** — registered with `cli.use()`
2. **Group-level middleware** — registered on mounted sub-CLIs
3. **Command-level middleware** — defined in command definition
4. **Command handler** — the `run()` function

```typescript theme={null}
const cli = Cli.create('my-cli')
  .use(async (c, next) => {
    console.log('CLI middleware')
    await next()
  })

const admin = Cli.create('admin')
  .use(async (c, next) => {
    console.log('Group middleware')
    await next()
  })
  .command('reset', {
    middleware: [
      async (c, next) => {
        console.log('Command middleware')
        await next()
      }
    ],
    run() {
      console.log('Command handler')
      return {}
    }
  })

cli.command(admin)

// Running "my-cli admin reset":
// CLI middleware
// Group middleware
// Command middleware
// Command handler
```
