DEV Community

brian austin
brian austin

Posted on

How to build a Claude-powered CLI chatbot in 30 lines of Node.js

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 quit or Ctrl+C
  • Works with any Claude model

Prerequisites

npm install @anthropic-ai/sdk readline
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Run it

export CLAUDE_API_KEY=your_key_here
node cli-chat.js
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Replace the client.messages.create call with:

const reply = await streamReply(history);
history.push({ role: 'assistant', content: reply });
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Add shebang to top of cli-chat.js:

#!/usr/bin/env node
Enter fullscreen mode Exit fullscreen mode

Install globally:

npm install -g .
Enter fullscreen mode Exit fullscreen mode

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 --file flag 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)