How to build a Claude-powered CLI chatbot in 30 lines of Node.js
Most Claude tutorials show you how to build a web app. But sometimes you just want to ask AI a question from your terminal — without opening a browser, without a UI, without anything except a prompt.
Here's how to build a fully functional Claude CLI chatbot with conversation memory in under 30 lines of Node.js.
What you'll build
- Interactive REPL loop in the terminal
- Persistent conversation memory (Claude remembers the context)
- Graceful exit on
quitorCtrl+C - Works with any Claude model
Prerequisites
npm install @anthropic-ai/sdk readline
You'll need a Claude API key. If you don't have one, you can use a flat-rate wrapper like SimplyLouie that gives you Claude access for $2/month.
The full code
// cli-chat.js
import Anthropic from '@anthropic-ai/sdk';
import readline from 'readline';
const client = new Anthropic({ apiKey: process.env.CLAUDE_API_KEY });
const history = [];
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function prompt() {
rl.question('You: ', async (input) => {
if (input.toLowerCase() === 'quit') {
console.log('Bye!');
rl.close();
return;
}
history.push({ role: 'user', content: input });
const response = await client.messages.create({
model: 'claude-3-5-haiku-20241022',
max_tokens: 1024,
messages: history
});
const reply = response.content[0].text;
history.push({ role: 'assistant', content: reply });
console.log(`Claude: ${reply}\n`);
prompt();
});
}
console.log('Claude CLI — type "quit" to exit\n');
prompt();
Run it
export CLAUDE_API_KEY=your_key_here
node cli-chat.js
Output:
Claude CLI — type "quit" to exit
You: what's the capital of France?
Claude: Paris.
You: what language do they speak there?
Claude: French. (Notice how it remembers we were talking about France.)
You: quit
Bye!
Add streaming so it feels instant
The version above waits for the full response before printing. Here's how to stream tokens as they arrive:
async function streamReply(history) {
process.stdout.write('Claude: ');
const stream = await client.messages.stream({
model: 'claude-3-5-haiku-20241022',
max_tokens: 1024,
messages: history
});
let fullText = '';
for await (const chunk of stream) {
if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
process.stdout.write(chunk.delta.text);
fullText += chunk.delta.text;
}
}
console.log('\n');
return fullText;
}
Replace the client.messages.create call with:
const reply = await streamReply(history);
history.push({ role: 'assistant', content: reply });
Now tokens print as they're generated — the same UX as Claude.ai in your terminal.
Keep conversation history from growing forever
If you chat for a long time, the history array grows and you'll eventually hit token limits. Trim it:
function trimHistory(history, maxTurns = 10) {
// Keep last N turns (each turn = 2 messages: user + assistant)
return history.slice(-(maxTurns * 2));
}
Call before each API request:
const trimmed = trimHistory(history);
const response = await client.messages.create({
model: 'claude-3-5-haiku-20241022',
max_tokens: 1024,
messages: trimmed
});
Add a system prompt to give Claude a persona
const response = await client.messages.create({
model: 'claude-3-5-haiku-20241022',
max_tokens: 1024,
system: 'You are a senior software engineer. Be concise. Give code examples when relevant.',
messages: trimmed
});
Now your CLI chatbot acts as a coding assistant, not a general-purpose assistant.
Package it as a global CLI command
Add to package.json:
{
"name": "my-claude-cli",
"version": "1.0.0",
"type": "module",
"bin": {
"ask": "./cli-chat.js"
}
}
Add shebang to top of cli-chat.js:
#!/usr/bin/env node
Install globally:
npm install -g .
Now you can run ask from anywhere on your system.
Cost
Claude 3.5 Haiku costs $0.80/million input tokens and $4/million output tokens. A typical CLI conversation is ~500 tokens — that's fractions of a cent per session.
If you want zero-math flat-rate access, SimplyLouie wraps the Claude API for $2/month with a simple REST endpoint — no API key management, no usage tracking, just POST and get a response.
What's next?
- Add a
--fileflag to pipe file contents into the conversation - Pipe stdin:
cat error.log | ask 'what does this error mean?' - Save conversation history to a JSON file for later review
What would you add to this CLI? Drop it in the comments.
Top comments (0)