Como criar uma CLI com React
Não é novidade que existem CLIs. Nem que elas podem ser criadas das mais diversas formas, desde algo pequeno e objetivo como um simples ls, até editores complexos e poderosos como o Neovim. Recentemente estive pesquisando sobre as novas ferramentas de IA e percebi um detalhe bem interessante sobre o Claude Code, Codex, Copilot... Todos utilizam React! E é isso que vou mostrar como fazer nesse artigo. TL;DR Nesse artigo vamos focar no mínimo necessário e o código final está disponível em jrmmendes/ink-cli-minimal-app. Caso procure uma aplicação um pouco mais completa, com alguns padrões adicionais e testes unitários, há um template disponível em jrmmendes/create-cli-app, que pode ser testado localmente: npm i -g @jrmmendes/create-cli-app create-cli-app --shell Veja a demo. Learn Once, Write Anywhere O meu objetivo não é falar sobre react de forma filosófica aqui, porem é útil refletir sobre a ideia central dessa tecnologia: leve o conhecimento sobre como criar interfaces de um lugar, para outro. Dado que a tecnologia é a mesma (React), em cada plataforma existe uma forma de utilizar os componentes básicos sobre os quais algo pode ser construído. Se está trabalhando com web, você utiliza o react-dom e elementos HTML como div e span; se está criando algo mobile, React Native e algo e suas tags serão Views. CLI? É aí que entra o ink - e vale comentar: não se trata de uma versão adaptada ou menor do React. Veja um exemplo: import { Box, Text } from 'ink'; export const Banner = (props: { name: string, version: string, cwd: string }) => { const ORANGE_COLOR = "#FE9900"; return ( React + Ink Example Application cwd: {props.cwd} {props.name} version: {props.version} ) }; Construindo a aplicação Vamos começar da forma mais direta possível: qual é o mínimo necessário para executar algo com Ink? Bem, você certamente vai precisar de um projeto javascript/jsx (e typescript, nessa caso), o que implica em ter um sistema de build com todas as etapas configuradas - a magia que o create-react-app e seus sucessores entregam em um único comando. Felizmente, estamos em 2025. Isso significa que o 7x1 foi há 11 anos, mas também que o bun, que resolve tudo de forma simples e performática, pode ser utilizado. Com efeito, executando: bun init -y Temos o esqueleto do projeto: . ├── bun.lock ├── index.ts ├── package.json ├── README.md └── tsconfig.json A partir disso, alguns itens precisam ser ajustados para que o build funcione corretamente no caso de uma aplicação CLI: Instalar dependências do React/Ink. Definir um (ou mais) caminhos de execução, através da chave bin; Definir quais arquivos serão utilizados para gerar o pacote npm durante a fase de publicação; Dependências É necessário instalar, além de ink e react, alguns pacotes para escrita de testes unitários (ink-testing-library) e as respectivas definições de tipo. Também utilizaremos o rimraf, para permitir limpar arquivos de build no futuro: bun i ink react@18 bun i -D @types/{ink,ink-testing-library,react} ink-testing-library rimraf react-devtools-core Configurações Crie um arquivo bin.js com o seguinte conteúdo: #!/bin/env node import('./dist/bundle.js'); Após isso, marque o arquivo como executável, o que pode ser feito em sistemas unix por meio do chmod: chmod +x bin.js Ajuste o package.json, adicionando o nome do executável e os arquivos do build: { "bin": { "ink-cli-app": "./bin.js" }, "files": [ "dist", "bin.js" ] } Também inclua alguns scripts para build, test e execução da aplicação: { "scripts": { "clear": "rimraf dist", "test": "FORCE_COLOR=0 NODE_ENV=test bun test", "build": "bun run clear && bun build src/application.tsx --outfile=./dist/bundle.js --target=node --format=esm --minify", "start": "node ./bin.js", "start:dev": "bun ./src/application.tsx", "prepublishOnly": "bun run build", } } Remova o arquivo index.ts e crie uma nova pasta src, com o arquivo application.tsx: import { exit } from 'process'; import { Box, render, Text } from 'ink'; import pkg from '../package.json'; type ApplicationProps = { name: string; version: string; } export const Application = ({ name, version }: ApplicationProps) => { const ORANGE_COLOR = "#FE9900"; return ( React + Ink Example Application {name} version: {version} ) } try { const { waitUntilExit } = render( ) await waitUntilExit(); } catch(error) { console.error({ status: 'app exited with error', error }); exit(1); } Por fim, basta executar os scripts build e start para ter a aplicação funcionando: Conclusão A partir desse ponto, não existe mais nada muito específico do ink: você pode utilizar o que já conhece de react para construir a aplicaç

Não é novidade que existem CLIs. Nem que elas podem ser criadas das mais diversas formas, desde algo pequeno e objetivo como um simples ls
, até editores complexos e poderosos como o Neovim.
Recentemente estive pesquisando sobre as novas ferramentas de IA e percebi um detalhe bem interessante sobre o Claude Code, Codex, Copilot... Todos utilizam React!
E é isso que vou mostrar como fazer nesse artigo.
TL;DR
Nesse artigo vamos focar no mínimo necessário e o código final está disponível em jrmmendes/ink-cli-minimal-app.
Caso procure uma aplicação um pouco mais completa, com alguns padrões adicionais e testes unitários, há um template disponível em jrmmendes/create-cli-app, que pode ser testado localmente:
npm i -g @jrmmendes/create-cli-app
create-cli-app --shell
Veja a demo.
Learn Once, Write Anywhere
O meu objetivo não é falar sobre react de forma filosófica aqui, porem é útil refletir sobre a ideia central dessa tecnologia: leve o conhecimento sobre como criar interfaces de um lugar, para outro.
Dado que a tecnologia é a mesma (React), em cada plataforma existe uma forma de utilizar os componentes básicos sobre os quais algo pode ser construído.
Se está trabalhando com web, você utiliza o react-dom e elementos HTML como div
e span
; se está criando algo mobile, React Native e algo e suas tags serão View
s. CLI? É aí que entra o ink - e vale comentar: não se trata de uma versão adaptada ou menor do React.
Veja um exemplo:
import { Box, Text } from 'ink';
export const Banner = (props: { name: string, version: string, cwd: string }) => {
const ORANGE_COLOR = "#FE9900";
return (
<Box
borderStyle="round"
paddingX={1}
borderColor={ORANGE_COLOR}
flexDirection="column"
>
<Box paddingBottom={1}>
<Text bold color={ORANGE_COLOR}>React + Ink Example ApplicationText>
Box>
<Text>cwd: {props.cwd}Text>
<Text>{props.name}Text>
<Text>version: {props.version}Text>
Box>
)
};
Construindo a aplicação
Vamos começar da forma mais direta possível: qual é o mínimo necessário para executar algo com Ink?
Bem, você certamente vai precisar de um projeto javascript/jsx (e typescript, nessa caso), o que implica em ter um sistema de build com todas as etapas configuradas - a magia que o create-react-app
e seus sucessores entregam em um único comando.
Felizmente, estamos em 2025. Isso significa que o 7x1 foi há 11 anos, mas também que o bun
, que resolve tudo de forma simples e performática, pode ser utilizado. Com efeito, executando:
bun init -y
Temos o esqueleto do projeto:
.
├── bun.lock
├── index.ts
├── package.json
├── README.md
└── tsconfig.json
A partir disso, alguns itens precisam ser ajustados para que o build funcione corretamente no caso de uma aplicação CLI:
- Instalar dependências do React/Ink.
- Definir um (ou mais) caminhos de execução, através da chave
bin
; - Definir quais arquivos serão utilizados para gerar o pacote npm durante a fase de publicação;
Dependências
É necessário instalar, além de ink e react, alguns pacotes para escrita de testes unitários (ink-testing-library) e as respectivas definições de tipo. Também utilizaremos o rimraf
, para permitir limpar arquivos de build no futuro:
bun i ink react@18
bun i -D @types/{ink,ink-testing-library,react} ink-testing-library rimraf react-devtools-core
Configurações
Crie um arquivo bin.js
com o seguinte conteúdo:
#!/bin/env node
import('./dist/bundle.js');
Após isso, marque o arquivo como executável, o que pode ser feito em sistemas unix por meio do chmod
:
chmod +x bin.js
Ajuste o package.json
, adicionando o nome do executável e os arquivos do build:
{
"bin": {
"ink-cli-app": "./bin.js"
},
"files": [
"dist",
"bin.js"
]
}
Também inclua alguns scripts para build, test e execução da aplicação:
{
"scripts": {
"clear": "rimraf dist",
"test": "FORCE_COLOR=0 NODE_ENV=test bun test",
"build": "bun run clear && bun build src/application.tsx --outfile=./dist/bundle.js --target=node --format=esm --minify",
"start": "node ./bin.js",
"start:dev": "bun ./src/application.tsx",
"prepublishOnly": "bun run build",
}
}
Remova o arquivo index.ts
e crie uma nova pasta src
, com o arquivo application.tsx
:
import { exit } from 'process';
import { Box, render, Text } from 'ink';
import pkg from '../package.json';
type ApplicationProps = {
name: string;
version: string;
}
export const Application = ({ name, version }: ApplicationProps) => {
const ORANGE_COLOR = "#FE9900";
return (
<Box width={80} paddingX={1} flexDirection="column">
<Box
borderStyle="round"
paddingX={1}
borderColor={ORANGE_COLOR}
flexDirection="column"
>
<Box paddingBottom={1}>
<Text bold color={ORANGE_COLOR}>React + Ink Example ApplicationText>
Box>
<Text>{name}Text>
<Text>version: {version}Text>
Box>
Box>
)
}
try {
const { waitUntilExit } = render(
<Application
name={pkg.name}
version={pkg.version}
/>
)
await waitUntilExit();
} catch(error) {
console.error({ status: 'app exited with error', error });
exit(1);
}
Por fim, basta executar os scripts build
e start
para ter a aplicação funcionando:
Conclusão
A partir desse ponto, não existe mais nada muito específico do ink: você pode utilizar o que já conhece de react para construir a aplicação.
Talvez seja útil dar um passo atrás e ler este guideline sobre como criar aplicações CLI com qualidade (escrito por autores de ferramentas populares, como docker compose).
Um outro componente que pode ser útil é um parser para os argumentos, bem como geração de documentação (o famoso --help
). Uma das soluções mais completas é o Commander, que abstrai grande parte desse trabalho.