CLI Project: Basic AI Assistant
Congratulations! You've learned about AI Engineering, LLMs, API setup, making calls, understanding tokens, and controlling AI behavior with parameters. Now it's time to put it all together and build something real - your very own AI assistant that runs in the command line.
This project will demonstrate everything you've learned so far and give you a solid foundation for the more advanced topics coming in module 2. Let's break that down step by step.
What We're Building
We're creating a simple but functional CLI application that:
- Accepts user questions from the command line
- Sends them to Google's Gemini API
- Displays AI responses in a clean, readable format
- Tracks token usage for each interaction
- Allows users to have multiple conversations in one session
- Provides a clean exit mechanism
Think of it as a basic version of ChatGPT that runs in your terminal. Here's what the interaction will look like:
π€ AI Assistant Ready! Type 'exit' to quit.
You: What is JavaScript?
π€ Thinking...
AI: JavaScript is a versatile programming language primarily used for web development...
π Token Usage: 45 input, 67 output (Total: 112)
You: How do I create a function?
π€ Thinking...
AI: In JavaScript, you can create functions in several ways...
π Token Usage: 38 input, 89 output (Total: 127)
You: exit
π Goodbye! Total session tokens: 239
Environment Setup
Before we start coding, let's set up our project structure and install the necessary dependencies.
First, create a new directory and initialize your TypeScript project:
mkdir ai-assistant-cli
cd ai-assistant-cli
npm init -y
Install the required dependencies:
npm install @google/genai dotenv
npm install -D typescript @types/node ts-node
Create your TypeScript configuration:
npx tsc --init
Update your tsconfig.json
to include these settings:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Create the basic project structure:
mkdir src
touch src/index.ts
touch .env
Add your Gemini API key to the .env
file:
GEMINI_API_KEY=your_api_key_here
Working Code Example
Now let's build our AI assistant step by step. We'll start with the basic imports and setup, then add the core functionality.
Step 1: Basic Setup and Imports
import { GoogleGenAI } from "@google/genai";
import * as readline from "readline";
import * as dotenv from "dotenv";
dotenv.config();
This sets up our essential imports. We're using the Google GenAI library for API calls, Node.js's built-in readline module for user input, and dotenv for environment variables.
Step 2: Initialize the AI Client
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
console.error("β GEMINI_API_KEY not found in environment variables");
console.log("Please add your API key to the .env file");
process.exit(1);
}
const genAI = new GoogleGenAI({ apiKey });
Here we're checking for the API key and creating our AI client instance. A common pitfall to watch out for is forgetting to check if the API key exists - this prevents confusing errors later.
Step 3: Create the Readline Interface
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
This creates our interface for reading user input from the terminal. The readline module is perfect for creating interactive CLI applications.
Step 4: Track Session Statistics
let totalTokensUsed = 0;
let conversationCount = 0;
We'll track these statistics throughout the session to give users insight into their API usage.
Step 5: Core AI Interaction Function
async function askAI(question: string): Promise<void> {
try {
console.log("\nπ€ Thinking...\n");
const response = await genAI.models.generateContent({
model: "gemini-2.5-flash",
contents: question,
config: {
temperature: 0.7,
},
});
console.log(`AI: ${response.text}\n`);
// Display token usage
const usage = response.usageMetadata;
if (usage) {
const inputTokens = usage.promptTokenCount || 0;
const outputTokens = usage.candidatesTokenCount || 0;
const sessionTotal = inputTokens + outputTokens;
totalTokensUsed += sessionTotal;
conversationCount++;
console.log(
`π Token Usage: ${inputTokens} input, ${outputTokens} output (Total: ${sessionTotal})\n`
);
}
} catch (error) {
console.error("β Error calling AI:", error);
console.log("Please try again.\n");
}
}
This function handles the core AI interaction. We're using a moderate temperature (0.7) for balanced responses and limiting output to 500 tokens to keep responses concise. The function also tracks and displays token usage after each response.
Step 6: Main Application Loop
function startAssistant(): void {
console.log("π€ AI Assistant Ready! Type 'exit' to quit.\n");
const askQuestion = (): void => {
rl.question("You: ", async (input) => {
const userInput = input.trim();
if (userInput.toLowerCase() === "exit") {
console.log(`\nπ Goodbye! Total session tokens: ${totalTokensUsed}`);
console.log(`Conversations: ${conversationCount}`);
rl.close();
return;
}
if (userInput === "") {
console.log("Please enter a question.\n");
askQuestion();
return;
}
await askAI(userInput);
askQuestion(); // Continue the conversation
});
};
askQuestion();
}
This creates our main application loop. It continuously prompts for user input, handles the special "exit" command, validates input, and maintains the conversation flow.
Step 7: Start the Application
// Handle graceful shutdown
process.on("SIGINT", () => {
console.log(`\n\nπ Goodbye! Total session tokens: ${totalTokensUsed}`);
console.log(`Conversations: ${conversationCount}`);
process.exit(0);
});
// Start the assistant
startAssistant();
This handles graceful shutdown when users press Ctrl+C and starts our assistant.
Step 8: Complete Application
Here's the complete code example you can run right away:
import { GoogleGenAI } from "@google/genai";
import * as readline from "readline";
import * as dotenv from "dotenv";
dotenv.config();
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
console.error("β GEMINI_API_KEY not found in environment variables");
console.log("Please add your API key to the .env file");
process.exit(1);
}
const genAI = new GoogleGenAI({ apiKey });
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
let totalTokensUsed = 0;
let conversationCount = 0;
async function askAI(question: string): Promise<void> {
try {
console.log("\nπ€ Thinking...\n");
const response = await genAI.models.generateContent({
model: "gemini-2.5-flash",
contents: question,
config: {
temperature: 0.7,
maxOutputTokens: 500,
},
});
console.log(`AI: ${response.text}\n`);
const usage = response.usageMetadata;
if (usage) {
const inputTokens = usage.promptTokenCount || 0;
const outputTokens = usage.candidatesTokenCount || 0;
const sessionTotal = inputTokens + outputTokens;
totalTokensUsed += sessionTotal;
conversationCount++;
console.log(
`π Token Usage: ${inputTokens} input, ${outputTokens} output (Total: ${sessionTotal})\n`
);
}
} catch (error) {
console.error("β Error calling AI:", error);
console.log("Please try again.\n");
}
}
function startAssistant(): void {
console.log("π€ AI Assistant Ready! Type 'exit' to quit.\n");
const askQuestion = (): void => {
rl.question("You: ", async (input) => {
const userInput = input.trim();
if (userInput.toLowerCase() === "exit") {
console.log(`\nπ Goodbye! Total session tokens: ${totalTokensUsed}`);
console.log(`Conversations: ${conversationCount}`);
rl.close();
return;
}
if (userInput === "") {
console.log("Please enter a question.\n");
askQuestion();
return;
}
await askAI(userInput);
askQuestion();
});
};
askQuestion();
}
process.on("SIGINT", () => {
console.log(`\n\nπ Goodbye! Total session tokens: ${totalTokensUsed}`);
console.log(`Conversations: ${conversationCount}`);
process.exit(0);
});
startAssistant();
Step 9: Running Your Assistant
Add this script to your package.json
:
{
"scripts": {
"start": "ts-node src/index.ts",
"build": "tsc",
"dev": "ts-node src/index.ts"
}
}
Now run your assistant:
npm run start
Understanding the Architecture
graph TD
A[Initialize Assistant] --> B[User Types Question]
B --> C[Send to Gemini API]
C --> D[Display AI Response]
D --> E[Show Token Usage]
E --> B
B -->|Type 'exit'| F[Show Session Stats & Exit]
This simple architecture diagram shows the core flow of our AI assistant:
- Initialize: Set up the API client and welcome the user
- Input Loop: Continuously accept user questions
- API Call: Send questions to Gemini and get responses
- Display: Show the AI response and token usage
- Repeat: Loop back for the next question
- Exit: Clean shutdown when user types 'exit'
The beauty of this CLI assistant lies in its simplicity - it's a straightforward request-response loop with proper initialization and cleanup.
FAQ
Summary
You've successfully built a complete AI-powered CLI assistant that demonstrates all the fundamentals from this module. The application handles user input, makes API calls to Gemini, processes responses, tracks token usage, and provides a smooth user experience. This project showcases practical applications of LLM integration, proper error handling, and real-world token management. You now have a solid foundation for building more complex AI applications and are ready to tackle advanced topics like conversation management and streaming responses in the next module.
Complete Code
You can find the complete, runnable code for this tutorial on GitHub: https://github.com/avestalabs/academy/tree/main/1-fundamentals/ai-assistant-cli