---
name: pikku-cli
description: 'Use when building CLI commands with Pikku. Covers wireCLI, pikkuCLICommand, subcommands, options, parameters, custom renderers, and nested command groups.
TRIGGER when: code uses wireCLI/pikkuCLICommand, user asks about CLI commands, terminal tools, command-line interface, or adding subcommands.
DO NOT TRIGGER when: user asks about the pikku CLI tool itself (use pikku-info) or HTTP endpoints (use pikku-http).'
installGroups: [core]
---

# Pikku CLI Wiring

## Agent Operating Procedure

Use this skill as an execution checklist, not reference material.

1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.

Wire Pikku functions as CLI commands with parameters, options, subcommands, and custom terminal renderers.

## Before You Start

```bash
pikku info functions --verbose   # See existing functions and their types
pikku info tags --verbose        # Understand project organization
```

See `pikku-concepts` for the core mental model.

## API Reference

### `wireCLI(config)`

```typescript
import { wireCLI } from '@pikku/core/cli'

wireCLI({
  program: string,              // Program name (e.g. 'todos')
  options?: {                   // Global options
    [key: string]: {
      description: string,
      short?: string,           // Single char alias (e.g. 'v')
      default?: any,
    }
  },
  render?: PikkuCLIRender,      // Default renderer for all commands
  commands: {
    [name: string]: PikkuCLICommand | {
      description: string,
      subcommands: { [name: string]: PikkuCLICommand }
    }
  },
})
```

### `pikkuCLICommand(config)`

```typescript
import { pikkuCLICommand } from '#pikku'

pikkuCLICommand({
  parameters?: string,          // Positional args (e.g. '<text>', '<username> <email>')
  func: PikkuFunc,              // Business logic function
  description?: string,
  render?: PikkuCLIRender,      // Custom output renderer
  options?: {
    [key: string]: {
      description: string,
      short?: string,
      default?: any,
      choices?: string[],       // Restrict to values
    }
  },
})
```

### `pikkuCLIRender(fn)`

```typescript
import { pikkuCLIRender } from '@pikku/core/cli'

const renderer = pikkuCLIRender<OutputType>((services, data) => {
  // Format and print output to terminal
  console.log(data)
})
```

## Usage Patterns

### Basic Commands

```typescript
wireCLI({
  program: 'todos',
  commands: {
    add: pikkuCLICommand({
      parameters: '<text>',
      func: createTodo,
      description: 'Add a new todo',
      render: todoRenderer,
      options: {
        priority: {
          description: 'Set priority',
          short: 'p',
          default: 'normal',
          choices: ['low', 'normal', 'high'],
        },
      },
    }),
    list: pikkuCLICommand({
      func: listTodos,
      description: 'List all todos',
      render: todosRenderer,
      options: {
        completed: {
          description: 'Show completed only',
          short: 'c',
          default: false,
        },
      },
    }),
  },
})
// Usage: todos add "Buy milk" -p high
// Usage: todos list -c
```

### Nested Subcommands

```typescript
wireCLI({
  program: 'app',
  options: {
    verbose: { description: 'Verbose output', short: 'v', default: false },
  },
  commands: {
    greet: pikkuCLICommand({
      parameters: '<name>',
      func: greetUser,
      render: greetRenderer,
    }),

    user: {
      description: 'User management',
      subcommands: {
        create: pikkuCLICommand({
          parameters: '<username> <email>',
          func: createUser,
          render: userRenderer,
          options: {
            admin: { description: 'Admin role', short: 'a', default: false },
          },
        }),
        list: pikkuCLICommand({
          func: listUsers,
          render: usersRenderer,
          options: {
            limit: { description: 'Max results', short: 'l' },
          },
        }),
      },
    },
  },
})
// Usage: app greet Alice
// Usage: app user create bob bob@example.com -a
// Usage: app user list -l 10
// Usage: app -v user list
```

### Custom Renderers

```typescript
const todoRenderer = pikkuCLIRender<{ todo: Todo }>((_services, { todo }) => {
  console.log(`✓ Created: ${todo.text} (priority: ${todo.priority})`)
})

const todosRenderer = pikkuCLIRender<{ todos: Todo[] }>(
  (_services, { todos }) => {
    todos.forEach((t, i) => {
      const check = t.completed ? '✓' : ' '
      console.log(`  ${i + 1}. ${t.text}  ${check}`)
    })
  }
)

// Default renderer for program, override per-command
wireCLI({
  program: 'todos',
  render: jsonRenderer,
  commands: {
    add: pikkuCLICommand({
      func: createTodo,
      render: todoRenderer, // Overrides jsonRenderer
    }),
  },
})
```

## Complete Example

```typescript
// functions/admin.functions.ts
export const createUser = pikkuFunc({
  title: 'Create User',
  func: async ({ db }, { username, email, admin }) => {
    const user = await db.createUser({
      username,
      email,
      role: admin ? 'admin' : 'user',
    })
    return { user }
  },
})

export const listUsers = pikkuSessionlessFunc({
  title: 'List Users',
  func: async ({ db }, { limit }) => {
    return { users: await db.listUsers(limit || 50) }
  },
})

export const deleteUser = pikkuFunc({
  title: 'Delete User',
  func: async ({ db }, { username }) => {
    await db.deleteUser(username)
    return { deleted: username }
  },
})

// wirings/cli.wiring.ts
const userRenderer = pikkuCLIRender<{ user: User }>((_services, { user }) => {
  console.log(`Created user: ${user.username} (${user.email}) [${user.role}]`)
})

const usersRenderer = pikkuCLIRender<{ users: User[] }>(
  (_services, { users }) => {
    console.log(`Users (${users.length}):`)
    users.forEach((u) =>
      console.log(`  ${u.username} <${u.email}> [${u.role}]`)
    )
  }
)

wireCLI({
  program: 'admin',
  commands: {
    user: {
      description: 'User management',
      subcommands: {
        create: pikkuCLICommand({
          parameters: '<username> <email>',
          func: createUser,
          render: userRenderer,
          options: {
            admin: {
              description: 'Create as admin',
              short: 'a',
              default: false,
            },
          },
        }),
        list: pikkuCLICommand({
          func: listUsers,
          render: usersRenderer,
          options: {
            limit: { description: 'Max results', short: 'l' },
          },
        }),
        delete: pikkuCLICommand({
          parameters: '<username>',
          func: deleteUser,
          description: 'Delete a user',
        }),
      },
    },
  },
})
```
