Skip to content

KurtGokhan/padrone

Repository files navigation

Padrone Logo

Create type-safe, interactive CLI apps with Zod schemas

npm version npm downloads license


✨ Features

  • πŸ”’ Type-safe - Full TypeScript support with Zod schema validation
  • 🎯 Fluent API - Chain commands, arguments, and options with a clean builder pattern
  • πŸ€– AI-Ready - First-class support for Vercel AI SDK tool integration
  • πŸ“š Auto Help - Automatic help generation from your schema definitions
  • 🧩 Nested Commands - Support for deeply nested subcommands
  • πŸ”„ Standard Schema - Built on Standard Schema for maximum compatibility
  • πŸš€ Zero Config - Works out of the box with sensible defaults

πŸ“¦ Installation

# Using npm
npm install padrone zod

# Using bun
bun add padrone zod

# Using pnpm
pnpm add padrone zod

# Using yarn
yarn add padrone zod

πŸš€ Quick Start

import { createPadrone } from 'padrone';
import * as z from 'zod/v4';

const program = createPadrone('myapp')
  .command('greet', (c) =>
    c
      .options(
        z.object({
          names: z.array(z.string()).describe('Names to greet'),
          prefix: z
            .string()
            .optional()
            .describe('Prefix to use in greeting')
            .meta({ alias: 'p' }),
        }),
        { positional: ['...names'] },
      )
      .action((options) => {
        const prefix = options?.prefix ? `${options.prefix} ` : '';
        options.names.forEach((name) => {
          console.log(`Hello, ${prefix}${name}!`);
        });
      }),
  );

// Run from CLI arguments
program.cli();

Running your CLI

# Run with arguments
myapp greet John Jane --prefix Mr.

# Or with alias
myapp greet John Jane -p Mr.

Output:

Hello, Mr. John!
Hello, Mr. Jane!

πŸ“– Usage Examples

Programmatic Execution

// Run a command directly with typed options
program.run('greet', { names: ['John', 'Jane'], prefix: 'Dr.' });

// Parse CLI input without executing
const parsed = program.parse('greet John --prefix Mr.');
console.log(parsed.options); // { names: ['John'], prefix: 'Mr.' }

API Mode

Generate a typed API from your CLI program:

const api = program.api();

// Call commands as functions with full type safety
api.greet({ names: ['Alice', 'Bob'], prefix: 'Dr.' });

Nested Commands

const program = createPadrone('weather')
  .command('forecast', (c) =>
    c
      .options(
        z.object({
          city: z.string().describe('City name'),
          days: z.number().optional().default(3).describe('Number of days'),
        }),
        { positional: ['city'] },
      )
      .action((options) => {
        console.log(`Forecast for ${options.city}: ${options.days} days`);
      })
      .command('extended', (c) =>
        c
          .options(
            z.object({
              city: z.string().describe('City name'),
            }),
            { positional: ['city'] },
          )
          .action((options) => {
            console.log(`Extended forecast for ${options.city}`);
          }),
      ),
  );

// Run nested command
program.cli('forecast extended London');

Option Aliases and Metadata

const program = createPadrone('app')
  .command('serve', (c) =>
    c
      .options(
        z.object({
          port: z
            .number()
            .default(3000)
            .describe('Port to listen on')
            .meta({ alias: 'p', examples: ['3000', '8080'] }),
          host: z
            .string()
            .default('localhost')
            .describe('Host to bind to')
            .meta({ alias: 'h' }),
          verbose: z
            .boolean()
            .optional()
            .describe('Enable verbose logging')
            .meta({ alias: 'v', deprecated: 'Use --debug instead' }),
        }),
      )
      .action((options) => {
        console.log(`Server running at ${options.host}:${options.port}`);
      }),
  );

Environment Variables and Config Files

Padrone supports loading options from environment variables and config files using dedicated schema methods:

const program = createPadrone('app')
  .command('serve', (c) =>
    c
      .options(
        z.object({
          port: z.number().default(3000).describe('Port to listen on'),
          apiKey: z.string().describe('API key for authentication'),
        }),
      )
      // Map environment variables to options
      .env(
        z
          .object({
            APP_PORT: z.coerce.number().optional(),
            API_KEY: z.string().optional(),
          })
          .transform((env) => ({
            port: env.APP_PORT,
            apiKey: env.API_KEY,
          })),
      )
      // Load config from JSON file with matching schema
      .configFile(
        'app.config.json',
        z.object({
          port: z.number().optional(),
          apiKey: z.string().optional(),
        }),
      )
      .action((options) => {
        console.log(`Server running on port ${options.port}`);
      }),
  );

Precedence order (highest to lowest): CLI args > environment variables > config file

πŸ€– AI SDK Integration

Padrone provides first-class support for the Vercel AI SDK, making it easy to expose your CLI as an AI tool:

import { streamText } from 'ai';
import { createPadrone } from 'padrone';
import * as z from 'zod/v4';

const weatherCli = createPadrone('weather')
  .command('current', (c) =>
    c
      .options(
        z.object({
          city: z.string().describe('City name'),
        }),
        { positional: ['city'] },
      )
      .action((options) => {
        return { city: options.city, temperature: 72, condition: 'Sunny' };
      }),
  );

// Convert your CLI to an AI tool
const result = await streamText({
  model: yourModel,
  prompt: "What's the weather in London?",
  tools: {
    weather: weatherCli.tool(),
  },
});

πŸ“‹ Auto-Generated Help

Padrone automatically generates help text from your Zod schemas:

console.log(program.help());

Example output:

Usage: myapp greet [names...] [options]

Arguments:
  names...    Names to greet

Options:
  -p, --prefix <string>   Prefix to use in greeting
  -h, --help              Show help

πŸ”§ API Reference

createPadrone(name)

Creates a new CLI program with the given name.

Program Methods

Method Description
.configure(config) Configure program properties (title, description, version)
.command(name, builder) Add a command to the program
.options(schema, meta?) Define options schema with optional positional args
.env(schema) Define schema for parsing environment variables into options
.configFile(file, schema?) Configure config file path(s) and schema
.action(handler) Set the command handler function
.cli(input?) Run as CLI (parses process.argv or input string)
.run(command, options) Run a command programmatically
.parse(input?) Parse input without executing
.stringify(command?, options?) Convert command and options back to CLI string
.api() Generate a typed API object
.help(command?) Generate help text
.tool() Generate a Vercel AI SDK tool
.find(command) Find a command by name

Options Meta

Use the second argument of .options() to configure positional arguments and per-option metadata:

.options(schema, {
  positional: ['source', '...files', 'dest'],  // '...files' is variadic
  options: {
    verbose: { alias: 'v' },
    format: { deprecated: 'Use --output instead' },
  },
})

Zod Meta Options

Use .meta() on Zod schemas to provide additional CLI metadata:

z.string().meta({
  alias: 'p',            // Short alias (-p)
  examples: ['value'],   // Example values for help text
  deprecated: 'message', // Mark as deprecated
  hidden: true,          // Hide from help output
})

πŸ› οΈ Requirements

  • Node.js 18+ or Bun
  • TypeScript 5.0+ (recommended)
  • Zod 3.25+ or 4.x

πŸ“„ License

MIT Β© Gokhan Kurt


Made with ❀️ by Gokhan Kurt