npm package: @sundaeswap/sprinkles
Description: A TypeScript library for building interactive CLI menus and TUI applications with TypeBox schema validation
Author: SundaeSwap Labs
Latest version: 0.8.2

# Sprinkles

> **Note:** This is an early release (v0.x). The API may change between minor versions as we refine the library based on community feedback.

A TypeScript library for building action-centric CLI applications with TypeBox schema validation. Define your actions once, and Sprinkles exposes them through three facades: an interactive **TUI**, a **CLI** with auto-generated flags and help, and an **MCP** server for AI agent integration.

## Architecture

Sprinkles follows an **action-centric** design. The core building block is an `IAction` — a typed, self-describing unit of work with input/output schemas:

```
                ┌──────────────┐
                │   Actions    │  ← define once
                │  (IAction)   │
                └──────┬───────┘
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
     ┌─────────┐ ┌─────────┐ ┌─────────┐
     │   TUI   │ │   CLI   │ │   MCP   │  ← three facades
     │ (menu)  │ │ (flags) │ │ (tools) │
     └─────────┘ └─────────┘ └─────────┘
```

- **TUI** — Interactive menu-driven interface. Users select actions from a menu, and Sprinkles auto-generates prompts from input schemas.
- **CLI** — Non-interactive command execution. Actions become subcommands with auto-generated `--flags` from input schemas and structured JSON output.
- **MCP** — Model Context Protocol server. Actions are exposed as tools that AI agents can discover and call with typed JSON input/output.

`Sprinkle.run()` detects the mode from `process.argv` and dispatches automatically.

## Features

- **Action-centric**: Define actions with TypeBox input/output schemas; get TUI, CLI, and MCP for free
- **Schema-driven UI**: Automatic prompt generation from TypeBox schemas
- **Multi-profile support**: Multiple named profiles with independent settings
- **Persistent settings**: JSON-based storage with BigInt support and optional encryption
- **14 built-in actions**: Profile management, settings, wallet operations, and transaction handling
- **Cardano integration**: Built-in helpers for Blaze SDK wallet and provider management
- **Type-safe**: Full TypeScript support with type inference from schemas

## Installation

```bash
npm install @sundaeswap/sprinkles
```

Optional peer dependencies:
- `@blaze-cardano/sdk` + `@blaze-cardano/query` — for wallet and transaction actions
- `@modelcontextprotocol/sdk` — for MCP mode

## Quick Start

```typescript
import { Type } from "@sinclair/typebox";
import type { TSchema } from "@sinclair/typebox";
import {
  Sprinkle,
  promptAndExecute,
  getBuiltinActions,
  NetworkSchema,
  ProviderSettingsSchema,
  WalletSettingsSchema,
} from "@sundaeswap/sprinkles";
import type { AnyAction, IAction, IMenu } from "@sundaeswap/sprinkles";

// 1. Define your settings schema
const AppSettings = Type.Object({
  network: NetworkSchema,
  provider: ProviderSettingsSchema,
  wallet: WalletSettingsSchema,
});

// 2. Define a custom action
const greetAction: IAction<
  { name: string },
  { greeting: string },
  TSchema
> = {
  name: "greet",
  description: "Say hello to someone.",
  category: "app",
  inputSchema: Type.Object({
    name: Type.String({ description: "Person to greet" }),
  }),
  outputSchema: Type.Object({
    greeting: Type.String(),
  }),
  execute: async (input) => ({
    greeting: `Hello, ${input.name}!`,
  }),
};

// 3. Define a TUI menu
const menu: IMenu<typeof AppSettings> = {
  title: "My App",
  items: [
    {
      title: "Greet someone",
      action: async (sprinkle) => {
        const result = await promptAndExecute(sprinkle, greetAction);
        if (result.success) {
          console.log(result.data.greeting);
        } else if (result.error.code !== "USER_CANCELLED") {
          console.error("Error:", result.error.message);
        }
      },
    },
  ],
};

// 4. Run — mode is detected automatically
await Sprinkle.run({
  type: AppSettings,
  storagePath: `${process.env["HOME"]}/.config/my-app`,
  menu,
  actions: [
    greetAction as unknown as AnyAction<typeof AppSettings>,
    ...getBuiltinActions<typeof AppSettings>(),
  ],
});
```

This single entry point gives you:

```bash
# TUI mode (interactive)
my-app

# CLI mode
my-app greet --name Alice
my-app list-profiles
my-app get-settings

# MCP mode (AI agent)
my-app --mcp

# Help
my-app --help
my-app greet --help
```

## Actions

### Defining Actions

An action is an object implementing `IAction<TInput, TOutput, TSettings>`:

```typescript
const myAction: IAction<
  { url: string; timeout?: number },
  { status: number },
  TSchema
> = {
  name: "check-health",
  description: "Check if a service is healthy.",
  category: "ops",
  inputSchema: Type.Object({
    url: Type.String({ description: "Service URL" }),
    timeout: Type.Optional(Type.Number({ description: "Timeout in ms", default: 5000 })),
  }),
  outputSchema: Type.Object({
    status: Type.Number(),
  }),
  execute: async (input, context) => {
    // context.settings gives you the current profile's settings
    // context.sprinkle gives you the Sprinkle instance
    const res = await fetch(input.url, { signal: AbortSignal.timeout(input.timeout ?? 5000) });
    return { status: res.status };
  },
};
```

### Built-in Actions

Sprinkles includes 14 built-in actions available via `getBuiltinActions()`:

| Category | Action | Description |
|----------|--------|-------------|
| sprinkles | `list-profiles` | List all profiles |
| sprinkles | `get-profile` | Get profile metadata and settings |
| sprinkles | `set-profile` | Switch active profile |
| sprinkles | `create-profile` | Create a new profile |
| sprinkles | `delete-profile` | Delete a profile |
| sprinkles | `get-settings` | Get current settings |
| sprinkles | `update-settings` | Update settings |
| wallet | `get-wallet-address` | Get wallet address |
| wallet | `get-wallet-balance` | Get ADA and token balances |
| wallet | `get-wallet-utxos` | Get UTxO set |
| wallet | `sign-transaction` | Sign a transaction (hot wallet only) |
| wallet | `submit-transaction` | Submit a signed transaction |
| wallet | `sign-and-submit` | Sign and submit in one step |
| transaction | `decode-transaction` | Decode transaction CBOR |

### Running Actions Programmatically

```typescript
import { executeAction } from "@sundaeswap/sprinkles";

const result = await executeAction(myAction, { url: "https://example.com" }, context);
if (result.success) {
  console.log(result.data.status);
} else {
  console.error(result.error.code, result.error.message);
}
```

## Built-in Schemas

Sprinkles exports common Cardano schemas for use in your settings:

### `NetworkSchema`

```typescript
Type.Union([Type.Literal("mainnet"), Type.Literal("preview"), Type.Literal("preprod")])
```

### `ProviderSettingsSchema`

```typescript
Type.Union([
  Type.Object({
    type: Type.Literal("blockfrost"),
    projectId: Type.String({ minLength: 1, title: "Blockfrost Project ID" }),
  }),
  Type.Object({
    type: Type.Literal("maestro"),
    apiKey: Type.String({ minLength: 1, title: "Maestro API Key" }),
  }),
])
```

### `WalletSettingsSchema`

```typescript
Type.Union([
  Type.Object({
    type: Type.Literal("hot"),
    privateKey: Type.String({ minLength: 1, title: "Hot Wallet Private Key", sensitive: true }),
  }),
  Type.Object({
    type: Type.Literal("cold"),
    address: Type.String({ minLength: 1, title: "Cold Wallet Address" }),
  }),
])
```

## Examples

See [`examples/`](./examples/) for runnable examples:

- **[`demo-app/`](./examples/demo-app/)** — Full-featured testing harness exercising all built-in actions across TUI, CLI, and MCP modes
- **[`action-example.ts`](./examples/action-example.ts)** — Multi-mode example with a custom action
- **[`action-in-menu.ts`](./examples/action-in-menu.ts)** — TUI menu integration patterns

## License

MIT