> ## 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

> Composable before/after hooks with typed dependency injection

Register composable before/after hooks with `cli.use()`. Middleware executes in registration order, onion-style—each calls `await next()` to proceed to the next middleware or the command handler.

## Basic Middleware

Use middleware for cross-cutting concerns like timing, logging, or auth:

```ts theme={null}
import { Cli } from 'incur'

const cli = Cli.create('deploy-cli', { description: 'Deploy tools' })
  .use(async (c, next) => {
    const start = Date.now()
    await next()
    console.log(`took ${Date.now() - start}ms`)
  })
  .command('deploy', {
    run() {
      return { deployed: true }
    },
  })

cli.serve()
```

**Output**

```sh theme={null}
$ deploy-cli deploy
deployed: true
took 12ms
```

## Middleware Context

Middleware receives a context object with metadata:

```ts theme={null}
import { Cli } from 'incur'

Cli.create('my-cli', { description: 'My CLI' })
  .use(async (c, next) => {
    console.log(`Running: ${c.command}`)
    console.log(`CLI: ${c.name}`)
    console.log(`Agent: ${c.agent}`)
    await next()
  })
  .command('deploy', {
    run() {
      return { deployed: true }
    },
  })
  .serve()
```

**Output**

```sh theme={null}
$ my-cli deploy
Running: deploy
CLI: my-cli
Agent: false
deployed: true
```

### Context Properties

| Property            | Type                  | Description                          |
| ------------------- | --------------------- | ------------------------------------ |
| `c.name`            | `string`              | The CLI name                         |
| `c.command`         | `string`              | The resolved command path            |
| `c.version`         | `string \| undefined` | CLI version                          |
| `c.agent`           | `boolean`             | Whether stdout is not a TTY          |
| `c.env`             | `object`              | Parsed environment variables         |
| `c.var`             | `object`              | Variables set by upstream middleware |
| `c.set(key, value)` | `function`            | Set a typed variable                 |
| `c.error(opts)`     | `function`            | Return an error result               |

## Authentication Middleware

Short-circuit the chain by returning early:

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

type User = { id: string; name: string }

const cli = Cli.create('my-cli', {
  description: 'My CLI',
  vars: z.object({ user: z.custom<User>() }),
})

const requireAuth = middleware<typeof cli.vars>((c, next) => {
  if (!c.var.user) {
    return c.error({ 
      code: 'AUTH', 
      message: 'must be logged in' 
    })
  }
  return next()
})

cli.command('deploy', {
  middleware: [requireAuth],
  run() {
    return { deployed: true }
  },
})

cli.serve()
```

**Output**

```sh theme={null}
$ my-cli deploy
Error (AUTH): must be logged in
```

## Dependency Injection with Variables

Declare a `vars` schema to enable typed variables. Middleware sets them with `c.set()`, and both middleware and command handlers read them via `c.var`:

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

type User = { id: string; name: string }

async function authenticate(): Promise<User> {
  return { id: 'u_123', name: 'Alice' }
}

const cli = Cli.create('my-cli', {
  description: 'My CLI',
  vars: z.object({
    user: z.custom<User>(),
    requestId: z.string(),
    debug: z.boolean().default(true),
  }),
})

cli.use(async (c, next) => {
  c.set('user', await authenticate())
  c.set('requestId', crypto.randomUUID())
  await next()
})

cli.command('whoami', {
  run(c) {
    return { 
      user: c.var.user, 
      requestId: c.var.requestId, 
      debug: c.var.debug 
    }
  },
})

cli.serve()
```

**Output**

```sh theme={null}
$ my-cli whoami
user:
  id: u_123
  name: Alice
requestId: 550e8400-e29b-41d4-a716-446655440000
debug: true
```

### Default Values

Use `.default()` for variables that don't need middleware:

```ts theme={null}
const cli = Cli.create('my-cli', {
  vars: z.object({
    debug: z.boolean().default(true),
    user: z.custom<User>(),
  }),
})

cli.command('test', {
  run(c) {
    // c.var.debug is always available
    // c.var.user must be set by middleware
    return { debug: c.var.debug }
  },
})
```

## Per-Command Middleware

Run middleware only for specific commands:

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

type User = { id: string; name: string; admin: boolean }

const cli = Cli.create('my-cli', {
  description: 'My CLI',
  vars: z.object({ user: z.custom<User>() }),
})

const requireAuth = middleware<typeof cli.vars>((c, next) => {
  if (!c.var.user) {
    return c.error({ code: 'AUTH', message: 'must be logged in' })
  }
  return next()
})

const requireAdmin = middleware<typeof cli.vars>((c, next) => {
  if (!c.var.user?.admin) {
    throw new Error('admin required')
  }
  return next()
})

cli
  .command('deploy', {
    middleware: [requireAuth],
    run() {
      return { deployed: true }
    },
  })
  .command('delete', {
    middleware: [requireAuth, requireAdmin],
    run() {
      return { deleted: true }
    },
  })
  .command('status', {
    // No auth required
    run() {
      return { status: 'ok' }
    },
  })

cli.serve()
```

**Output**

```sh theme={null}
$ my-cli deploy
Error (AUTH): must be logged in

$ my-cli status
status: ok
```

Per-command middleware runs **after** root and group middleware.

## Error Handling

Middleware can handle errors from downstream middleware or commands:

```ts theme={null}
import { Cli } from 'incur'

Cli.create('my-cli', { description: 'My CLI' })
  .use(async (c, next) => {
    try {
      await next()
    } catch (error) {
      console.error('Caught error:', error)
      throw error // Re-throw or handle
    }
  })
  .command('fail', {
    run() {
      throw new Error('Something went wrong')
    },
  })
  .serve()
```

### Structured Errors

Return structured errors with error codes:

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

const requireAuth = middleware((c, next) => {
  if (!authenticated()) {
    return c.error({
      code: 'AUTH',
      message: 'must be logged in',
      retryable: false,
    })
  }
  return next()
})
```

**Output**

```sh theme={null}
$ my-cli deploy
Error (AUTH): must be logged in
```

Structured errors show up in the output envelope with code and retryable flag.

### Throwing Errors

Throwing also works and produces an `UNKNOWN` error code:

```ts theme={null}
const requireAdmin = middleware<typeof cli.vars>((c, next) => {
  if (!c.var.user?.admin) throw new Error('admin required')
  return next()
})
```

## Typed Middleware

Use the `middleware()` helper for full type safety:

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

type User = { id: string; name: string }

const vars = z.object({
  user: z.custom<User>(),
  requestId: z.string(),
})

const env = z.object({
  API_KEY: z.string(),
})

// Pass vars and env schemas as generics
const myMiddleware = middleware<typeof vars, typeof env>((c, next) => {
  // c.var is typed as { user: User, requestId: string }
  // c.set() is typed to accept only valid keys
  // c.env is typed as { API_KEY: string }
  
  c.set('requestId', crypto.randomUUID())
  return next()
})
```

## Environment Variables

Access parsed environment variables in middleware:

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

const cli = Cli.create('my-cli', {
  description: 'My CLI',
  env: z.object({
    API_KEY: z.string(),
    DEBUG: z.boolean().default(false),
  }),
})

const logEnv = middleware<typeof cli.vars, typeof cli.env>((c, next) => {
  console.log('API_KEY:', c.env.API_KEY)
  console.log('DEBUG:', c.env.DEBUG)
  return next()
})

cli.use(logEnv)
```

Environment variables are validated once at startup before any middleware runs.

## Middleware Order

Middleware executes in an onion pattern:

```ts theme={null}
import { Cli } from 'incur'

Cli.create('my-cli', { description: 'My 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')
  })
  .command('test', {
    run() {
      console.log('command')
      return { ok: true }
    },
  })
  .serve()
```

**Output**

```sh theme={null}
$ my-cli test
1: before
2: before
command
2: after
1: after
ok: true
```

### Execution Order

1. Root CLI middleware (in registration order)
2. Group CLI middleware (if mounted)
3. Per-command middleware (if defined)
4. Command `run` handler

## Source Code

The middleware implementation is in `src/middleware.ts`:

```ts theme={null}
export 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

export default 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> {
  return handler
}
```

<Tip>Use middleware for cross-cutting concerns like auth, logging, and timing. Use variables (`c.var` and `c.set()`) for type-safe dependency injection.</Tip>
