Get the FREE Ultimate OpenClaw Setup Guide →

ink-component-generator

npx machina-cli add skill a5c-ai/babysitter/ink-component-generator --openclaw
Files (1)
SKILL.md
5.2 KB

Ink Component Generator

Generate Ink (React) components for terminal UIs.

Capabilities

  • Generate Ink React components
  • Create custom hooks for CLI state
  • Set up layout components (Box, Text)
  • Implement input handling
  • Create loading and progress components
  • Set up testing with ink-testing-library

Usage

Invoke this skill when you need to:

  • Build terminal UIs with React patterns
  • Create interactive CLI components
  • Implement stateful terminal interfaces
  • Set up Ink project structure

Inputs

ParameterTypeRequiredDescription
projectNamestringYesProject name
componentsarrayYesComponent definitions
includeHooksbooleanNoGenerate custom hooks

Component Structure

{
  "components": [
    {
      "name": "SelectList",
      "type": "interactive",
      "props": ["items", "onSelect"],
      "state": ["selectedIndex"]
    }
  ]
}

Generated Patterns

Select List Component

import React, { useState, useCallback } from 'react';
import { Box, Text, useInput, useApp } from 'ink';

interface SelectListProps {
  items: string[];
  onSelect: (item: string, index: number) => void;
}

export const SelectList: React.FC<SelectListProps> = ({ items, onSelect }) => {
  const [selectedIndex, setSelectedIndex] = useState(0);
  const { exit } = useApp();

  useInput((input, key) => {
    if (key.upArrow) {
      setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));
    } else if (key.downArrow) {
      setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));
    } else if (key.return) {
      onSelect(items[selectedIndex], selectedIndex);
    } else if (input === 'q' || key.escape) {
      exit();
    }
  });

  return (
    <Box flexDirection="column">
      {items.map((item, index) => (
        <Box key={item}>
          <Text color={index === selectedIndex ? 'green' : undefined}>
            {index === selectedIndex ? '> ' : '  '}
            {item}
          </Text>
        </Box>
      ))}
      <Box marginTop={1}>
        <Text dimColor>Use arrow keys to navigate, Enter to select, q to quit</Text>
      </Box>
    </Box>
  );
};

Text Input Component

import React, { useState } from 'react';
import { Box, Text, useInput } from 'ink';

interface TextInputProps {
  placeholder?: string;
  onSubmit: (value: string) => void;
  mask?: string;
}

export const TextInput: React.FC<TextInputProps> = ({
  placeholder = '',
  onSubmit,
  mask,
}) => {
  const [value, setValue] = useState('');
  const [cursor, setCursor] = useState(0);

  useInput((input, key) => {
    if (key.return) {
      onSubmit(value);
      return;
    }
    if (key.backspace || key.delete) {
      setValue((prev) => prev.slice(0, -1));
      setCursor((prev) => Math.max(0, prev - 1));
      return;
    }
    if (!key.ctrl && !key.meta && input) {
      setValue((prev) => prev + input);
      setCursor((prev) => prev + 1);
    }
  });

  const displayValue = mask ? mask.repeat(value.length) : value;

  return (
    <Box>
      <Text>
        {displayValue || <Text dimColor>{placeholder}</Text>}
        <Text backgroundColor="white"> </Text>
      </Text>
    </Box>
  );
};

Spinner Component

import React, { useState, useEffect } from 'react';
import { Text } from 'ink';

const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];

interface SpinnerProps {
  label?: string;
}

export const Spinner: React.FC<SpinnerProps> = ({ label }) => {
  const [frame, setFrame] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setFrame((prev) => (prev + 1) % frames.length);
    }, 80);
    return () => clearInterval(timer);
  }, []);

  return (
    <Text>
      <Text color="green">{frames[frame]}</Text>
      {label && <Text> {label}</Text>}
    </Text>
  );
};

Custom Hook - useAsync

import { useState, useEffect, useCallback } from 'react';

interface AsyncState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

export function useAsync<T>(
  asyncFn: () => Promise<T>,
  deps: any[] = []
): AsyncState<T> & { refetch: () => void } {
  const [state, setState] = useState<AsyncState<T>>({
    data: null,
    loading: true,
    error: null,
  });

  const execute = useCallback(async () => {
    setState({ data: null, loading: true, error: null });
    try {
      const data = await asyncFn();
      setState({ data, loading: false, error: null });
    } catch (error) {
      setState({ data: null, loading: false, error: error as Error });
    }
  }, deps);

  useEffect(() => {
    execute();
  }, [execute]);

  return { ...state, refetch: execute };
}

Dependencies

{
  "dependencies": {
    "ink": "^4.0.0",
    "react": "^18.0.0"
  },
  "devDependencies": {
    "@types/react": "^18.0.0",
    "ink-testing-library": "^3.0.0"
  }
}

Target Processes

  • tui-application-framework
  • interactive-form-implementation
  • dashboard-monitoring-tui

Source

git clone https://github.com/a5c-ai/babysitter/blob/main/plugins/babysitter/skills/babysit/process/specializations/cli-mcp-development/skills/ink-component-generator/SKILL.mdView on GitHub

Overview

Ink Component Generator creates Ink React components for terminal UIs. It scaffolds components with hooks for state, layout primitives like Box and Text, and input handling. It also supports loading/progress components and testing via ink-testing-library.

How This Skill Works

Provide projectName, components, and optional includeHooks. The generator scaffolds Ink React components, wires up state with React hooks and Ink's input APIs, and outputs layout primitives (Box, Text) along with interactive patterns and optional tests.

When to Use It

  • Build terminal UIs with React patterns for CLI apps
  • Create interactive CLI components like select lists and text inputs
  • Set up a complete Ink project structure with components and tests
  • Implement keyboard input handling and state management in terminal UIs
  • Add loading and progress visuals and test coverage for Ink components

Quick Start

  1. Step 1: Define projectName and component definitions for the generator
  2. Step 2: (Optional) set includeHooks to true to generate custom hooks
  3. Step 3: Run the generator to scaffold Ink components and tests, then integrate into your Ink project

Best Practices

  • Plan components upfront with clear props and state boundaries
  • Leverage useInput and useApp for robust keyboard handling
  • Keep component responsibilities small and reusable
  • Document keyboard shortcuts and accessibility cues in the UI
  • Write tests with ink-testing-library to cover rendering and interactions

Example Use Cases

  • Terminal file picker and list navigation
  • CLI multi-step wizard with progress indicators
  • Interactive select menus for options
  • Loading spinners and progress bars during operations
  • Form-like inputs and real-time updates in terminal apps

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers