/cli-framework-oclif-ink
Modern CLI development combining oclif's command framework with Ink's React-based terminal rendering
$ npx -y skills add agents-inc/skills --skill cli-framework-oclif-ink --agent claude-codeHow 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
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.mdname: 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 { ComRead more
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 { ComShowing the first part of this file.
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?
Repo: agents-inc/skills
Other skills on agents-inc-skills.
- /ai-infrastructure-huggingface-inference
Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints
Open skill - /ai-infrastructure-litellm
LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment
Open skill - /ai-infrastructure-modal
Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns
Open skill - /ai-infrastructure-ollama
Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint
Open skill - /ai-infrastructure-replicate
Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training
Open skill - /ai-infrastructure-together-ai
Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints
Open skill

