Skip to content

/cli-framework-oclif-ink

Modern CLI development combining oclif's command framework with Ink's React-based terminal rendering

shell
$ npx -y skills add agents-inc/skills --skill cli-framework-oclif-ink --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/cli-framework-oclif-ink
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

Modern CLI development combining oclif's command framework with Ink's React-based terminal rendering

SKILL.md

cli-framework-oclif-ink.SKILL.md
name: cli-framework-oclif-ink
description: Modern CLI development combining oclif's command framework with Ink's React-based terminal rendering

oclif + Ink CLI Patterns

> **Quick Guide:** Use oclif for command routing, flag/arg parsing, and plugin architecture. Use Ink for React-based interactive terminal UIs with Flexbox layout. Combine both when commands need rich stateful interfaces. Always `await waitUntilExit()` when rendering Ink from oclif commands. Use `this.log()` instead of `console.log` to preserve JSON output mode.

---

<critical_requirements>

CRITICAL: Before Using This Skill

> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)

**(You MUST `await waitUntilExit()` after `render()` in oclif commands -- without it the process exits before the UI completes)**

**(You MUST use `this.log()` / `this.warn()` / `this.error()` in commands -- `console.log` breaks `--json` mode and test capture)**

**(You MUST wrap all text in `<Text>` components in Ink -- bare strings cause rendering errors)**

**(You MUST use `useEffect` cleanup to cancel async operations -- Ink components unmount when the user presses Ctrl+C)**

</critical_requirements>

---

**Auto-detection:** oclif, @oclif/core, @oclif/test, Ink, ink, @inkjs/ui, Command class, Flags, Args, useInput, useApp, useFocus, render(), waitUntilExit, terminal UI, CLI command, ink-testing-library

**When to use:**

  • Building multi-command CLIs with flag/arg parsing
  • Creating interactive terminal UIs (wizards, dashboards, progress displays)
  • Combining command routing with rich React-based interfaces
  • Building plugin-extensible CLI architectures

**When NOT to use:**

  • Simple one-off scripts (plain Node.js suffices)
  • Basic prompts only (a lightweight prompt library suffices)
  • Performance-critical startup under 100ms (oclif adds ~200ms overhead)

**Key patterns covered:**

  • oclif command structure with typed flags, args, and output methods
  • Ink components, Flexbox layout, keyboard input, and focus management
  • Integration: rendering Ink from oclif commands with lifecycle management
  • @inkjs/ui pre-built components (Select, TextInput, Spinner, etc.)
  • Plugin architecture and lifecycle hooks
  • Multi-step wizards, progress indicators, and cancelable operations
  • Testing commands with `@oclif/test` and components with `ink-testing-library`

---

<philosophy>

Philosophy

oclif and Ink solve orthogonal problems. **oclif** handles the boring-but-critical parts: command routing, flag parsing, help generation, plugin discovery, auto-updates. **Ink** handles the interactive parts: stateful terminal UIs using React's component model with Flexbox layout.

**Use oclif alone** when commands do their work and print output. **Add Ink** when a command needs real-time user interaction (wizards, dashboards, progress). The integration point is simple: the oclif command's `run()` calls `render()` and awaits `waitUntilExit()`.

**Key architectural decisions:**

  • Commands are `.ts` files (not `.tsx`) -- they import Ink components from separate `.tsx` files
  • oclif handles process lifecycle; Ink handles UI lifecycle within it
  • Keyboard handling lives in Ink components via `useInput`, not in oclif commands
  • State management for complex Ink UIs should use an external store (not prop drilling)

</philosophy>

---

<patterns>

Core Patterns

Pattern 1: oclif Command with Typed Flags and Args

Commands use static properties for metadata and flag/arg definitions. The `run()` method is async and returns typed data for JSON output support.

import { Command, Flags, Args } from "@oclif/core";

const DEFAULT_RETRIES = 3;

export class Deploy extends Command {
  static summary = "Deploy to target environment";
  static enableJsonFlag = true; // Adds --json flag

  static flags = {
    env: Flags.string({
      char: "e",
      required: true,
      options: ["staging", "production"] as const,
    }),
    retries: Flags.integer({
      char: "r",
      default: DEFAULT_RETRIES,
      min: 0,
      max: 10,
    }),
    verbose: Flags.boolean({ char: "v", default: false, allowNo: true }),
    apiKey: Flags.string({ env: "MY_CLI_API_KEY" }), // From env var
  };

  static args = {
    target: Args.string({ description: "Deploy target", required: true }),
  };

  async run(): Promise<{ status: string }> {
    const { args, flags } = await this.parse(Deploy);
    // Use this.log, this.warn, this.error -- never console.*
    this.log(`Deploying ${args.target} to ${flags.env}`);
    return { status: "deployed" };
  }
}

See [examples/core.md](examples/core.md) Pattern 1-5 for complete flag types, args, output methods, and error handling.

---

Pattern 2: Ink Component with Keyboard Handling

Ink components are React functional components using hooks for input, app lifecycle, and focus.

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

interface SelectorProps {
  items: string[];
  onSelect: (item: string) => void;
}

export const Selector: React.FC<SelectorProps> = ({ items, onSelect }) => {
  const [index, setIndex] = useState(0);
  const { exit } = useApp();

  useInput((input, key) => {
    if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
    if (key.downArrow) setIndex((i) => Math.min(items.length - 1, i + 1));
    if (key.return) onSelect(items[index]);
    if (input === "q") exit();
  });

  return (
    <Box flexDirection="column">
      {items.map((item, i) => (
        <Text key={item} bold={i === index}>
          {i === index ? "> " : "  "}
          {item}
        </Text>
      ))}
    </Box>
  );
};

See [examples/core.md](examples/core.md) Pattern 6-8 for styling, layout, and @inkjs/ui components.

---

Pattern 3: Rendering Ink from oclif Command

The integration pattern: oclif command renders an Ink component and awaits its completion.

import { Com
Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withagents-inc-skills

The official skills marketplace for Agents Inc. 150+ skills covering everything from React and Prisma to Redis, ElevenLabs, and infrastructure tooling. Pick the skills that match your stack and install them via Claude Code. Need more control?

Get the whole plugin, auto-invoked