Build Your First AI Chatbot CLI
Build a terminal chatbot powered by Claude with memory and token logging.
Introduction
30 Second Summary
Every time you use a chatbot, a simple loop is running behind the scenes. Your message goes out, the AI responds, and the full conversation gets sent back each time so the AI remembers what you said.
In this project, you will build your own chatbot powered by Claude that runs directly in your terminal. You'll connect to the API, manage conversation memory, and log token usage for every request.
What You'll Build
A terminal-based chatbot powered by Claude that maintains conversation memory and logs token usage and response time for every API call.
By the end of this project, you'll have:
- A Python project with a virtual environment and the Anthropic SDK installed.
- A multi-turn chatbot that remembers your full conversation history between messages.
- Token and latency logging that shows exactly what each API call costs in time and tokens.
- Secret Mission: a configurable system prompt and a /save command that exports conversations to a JSON log file.
Are there any prerequisites?
None. This project includes full setup for every tool you need. You just need a Mac with an internet connection.
Install VS Code and Set Up Your Python Project
Every project needs two things: a code editor to write in and a clean environment to run code.
In this step, you will install VS Code (a free editor with a built-in terminal), create a Python project folder, and install the Anthropic SDK that lets your code talk to Claude.
In this step, get ready to:
- Install VS Code and set up the code command in your terminal.
- Create a Python project with a virtual environment.
- Install the Anthropic SDK.
Download and install VS Code
VS Code is a free code editor with a built-in terminal. You will use it for the rest of this project to write and run Python code in one place.
- Open your browser and go to https://code.visualstudio.com.
- Click the download button for macOS. This downloads a .dmg file.
- Open the downloaded .dmg file.
- Drag Visual Studio Code into the Applications folder.
- Press Cmd+Space to open Spotlight.
- Type Visual Studio Code and press Enter to launch it.
What is VS Code?
VS Code is a lightweight code editor made by Microsoft. It has syntax highlighting, a built-in terminal, and thousands of extensions.
The built-in terminal means you can write code and run commands without switching between apps.
Now you need to install the code command so you can open projects from your terminal later.
- Press Cmd+Shift+P to open the Command Palette.
- Type shell command in the search bar.
- Select Shell Command: Install 'code' command in PATH.
What does this do?
This adds a code command to your terminal. Later, you can type code . from any folder to open it directly in VS Code.
Don't see the shell command option?
Make sure VS Code is fully launched before opening the Command Palette. If the option still does not appear, try quitting and reopening VS Code.
help me find the Shell Command option in VS Code's Command Palette
Create your project folder and install the SDK
You will create a dedicated folder for this project, set up a virtual environment to keep your packages isolated, and install the Anthropic SDK.
- Press Cmd+Space to open Spotlight.
- Type Terminal and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir claude-chatbot
cd claude-chatbot
- Confirm the folder exists by running:
ls ~/Desktop
You should see claude-chatbot in the output.
Next, create a virtual environment and activate it. This keeps the packages you install for this project separate from the rest of your system.
- Create and activate your virtual environment by running these commands:
python3 -m venv .venv
source .venv/bin/activate
What is a virtual environment?
A virtual environment is an isolated space where Python installs packages just for one project. Without it, every project on your computer would share the same packages and could conflict with each other.
The .venv folder holds this isolated environment. The source .venv/bin/activate command tells your terminal to use it.
After activating, your terminal prompt changes to show (.venv) at the beginning. This confirms you are inside the virtual environment.
- Install the Anthropic SDK by running:
pip install anthropic==0.109.2
What is the Anthropic SDK?
An SDK (Software Development Kit) is a library that makes it easy to talk to an API. The anthropic package handles all the network requests to Claude's servers so you can send messages with just a few lines of Python.
You should see pip download and install the package. It finishes with a success message.
Seeing a permissions error or network timeout?
Make sure you see (.venv) in your terminal prompt. If you do not, run source .venv/bin/activate again before retrying the install.
If you see a network timeout, check your internet connection and try running the install command again.
help me fix a pip install error for the anthropic SDK
Open the project in VS Code and verify the setup
Now you will open your project folder in VS Code and confirm everything installed correctly.
- Open your project in VS Code by running:
code .
What does this command do?
The code . command tells VS Code to open the current folder (the . means "this directory"). VS Code launches with your claude-chatbot folder loaded in the sidebar.
VS Code may ask if you trust the authors of this folder. Click Yes, I trust the authors to continue.
- Verify the SDK installed correctly by running this command in your terminal:
python3 -c "import anthropic; print(anthropic.__version__)"
You should see 0.109.2 printed in the terminal.
Seeing ModuleNotFoundError?
This means your terminal is not using the virtual environment. Make sure you see (.venv) at the start of your prompt. If not, run source .venv/bin/activate from inside the claude-chatbot folder, then try the verification command again.
help me fix ModuleNotFoundError for the anthropic package
Your environment is ready. You have VS Code open, the Anthropic SDK installed, and a clean virtual environment to work in. Next up, you will get your API key and configure it so your code can talk to Claude.
Get Your API Key and Configure Environment Variables
Your Python project is set up and ready to go. Now you need a way to prove your identity to Anthropic's servers so they'll accept your requests.
In this step, you'll create an Anthropic Console account, generate an API key, and store it securely using environment variables.
In this step, get ready to:
- Create an Anthropic Console account and generate an API key.
- Store the key in a .env file and export it as an environment variable.
- Protect your secrets with a .gitignore file.
Create your Anthropic account and API key
Every request to Claude's API must include a secret string called an API key. This identifies you to Anthropic's servers and tracks your usage against your account's credits.
- Open your browser and go to https://console.anthropic.com.
- Create a new account (or sign in if you already have one).
- Navigate to API Keys in the left sidebar.
- Click Create Key.
- Give your key a name like chatbot-project and click Create Key.
- Copy the key immediately and paste it somewhere safe (like a notes app). You will not be able to see it again after closing this dialog.
What is an API key?
An API key is a secret string that identifies you to a service. Think of it like a password that your code uses to prove it belongs to your account.
New Anthropic accounts receive approximately $5 in free starter credits. The chatbot you're building uses Claude Haiku 4.5, which costs $1 per million input tokens and $5 per million output tokens. Your entire project will cost fractions of a cent.
Why not just paste the key in your code?
If you ever share your code (on GitHub, with a teammate, or in a portfolio), anyone who sees it can use your key and run up charges on your account. Storing it in an environment variable keeps it separate from your source code entirely.
Store your API key as an environment variable
The anthropic SDK automatically reads a variable called ANTHROPIC_API_KEY from your environment. You never need to type the key in your Python code.
- In VS Code, create a new file called .env in your claude-chatbot folder by clicking File then New File.
- Add the following content to .env:
ANTHROPIC_API_KEY=your-api-key-here
- Replace your-api-key-here with the actual API key you copied from the Anthropic Console.
- Save the file (Cmd+S on macOS or Ctrl+S on Windows).
What is a .env file?
A .env file is a simple text file that stores configuration values as key-value pairs. It acts as a reference for your project's secrets. The file itself does not automatically load into your terminal session.
Now you need to export the key into your active terminal session so the SDK can find it.
- Switch back to the VS Code integrated terminal (or open it with Ctrl+`).
- Export your API key by running this command:
export ANTHROPIC_API_KEY="your-actual-key-here"
- Replace your-actual-key-here with the real key you copied earlier.
What does this command do?
The export command makes the variable available to any program you run in this terminal session. When you later run your Python script, the anthropic SDK will automatically pick up ANTHROPIC_API_KEY without you writing any credential-loading code.
- Verify the variable is set by running:
echo $ANTHROPIC_API_KEY
You should see your API key printed in the terminal output.
Don't see your key printed?
- Make sure you included the quotes around your key in the export command.
- Check that you ran the export command in the same terminal session where you are running echo. If you opened a new terminal tab, the variable will not carry over.
- Confirm you copied the full key from the Anthropic Console (it should start with sk-ant-).
Help me troubleshoot my ANTHROPIC_API_KEY environment variable.
Create a .gitignore to protect your secrets
A .gitignore file tells Git which files to never track or upload. This prevents you from accidentally pushing your API key to a public repository.
- Create a new file called .gitignore in your claude-chatbot folder.
- Add the following content:
.env
.venv/
__pycache__/
chat_log_*.json
What do these entries mean?
- .env keeps your API key out of version control.
- .venv/ excludes the virtual environment (it is large and machine-specific).
- __pycache__/ excludes Python's compiled bytecode files.
- chat_log_*.json excludes conversation log files you may generate later.
- Save .gitignore.
- Confirm both files exist by running:
ls -a
You should see .env and .gitignore listed alongside .venv in the output.
Don't see .env or .gitignore?
- The -a flag shows hidden files (those starting with a dot). If you ran ls without it, dotfiles will not appear.
- Make sure you are in the claude-chatbot directory. Run pwd to check your current location.
Help me find my .env and .gitignore files.
✔️ Awesome, I've got everything!
Great. Double check you have saved both files before moving on.
ⓧ I'd like to double check the full code
Here is what each file should look like:
ANTHROPIC_API_KEY=your-api-key-here
.env
.venv/
__pycache__/
chat_log_*.json
Your credentials are locked down and ready to use. Next up, you'll write your first lines of Python that actually talk to Claude.
Send Your First Message to Claude
Your project environment is set up and your API key is ready to go.
Now it is time to write actual code that talks to Claude. You will send a single message, get a response, and see exactly how many tokens the exchange consumed.
In this step, get ready to:
- Send your first API request to Claude.
- Print Claude's response in your terminal.
- Examine token usage from the API response.
Create chat.py and send your first message
Every AI API works the same way. You send a request containing a model name, a list of messages, and constraints like a maximum token limit. The API returns a response object with the model's generated text plus metadata.
- Create a new file called chat.py in your claude-chatbot folder and add the following code:
import os
import time
import anthropic
# Create a client (reads ANTHROPIC_API_KEY from environment automatically)
client = anthropic.Anthropic()
# Send a single message to Claude
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude!"}],
)
# Print Claude's response text
print(response.content[0].text)
# Print token usage
print(f"Input tokens: {response.usage.input_tokens}")
print(f"Output tokens: {response.usage.output_tokens}")
What does this code do?
- anthropic.Anthropic() creates a client that automatically reads your ANTHROPIC_API_KEY environment variable. No need to pass the key in code.
- client.messages.create() sends a request to the Messages API. You specify the model (claude-haiku-4-5), a token limit, and a list of messages.
- The messages parameter is an array of objects, each with a role ("user" or "assistant") and content (the text). For a single query, you send one user message.
- response.content[0].text extracts Claude's text reply from the response object.
- response.usage.input_tokens and response.usage.output_tokens show how many tokens were consumed by your request and Claude's reply.
What are tokens?
Tokens are the units LLMs use to process text. A token is roughly 3/4 of a word, so "Hello, how are you?" is about 6 tokens.
You are billed per token for both input (what you send) and output (what Claude generates). Claude Haiku 4.5 costs $1 per million input tokens and $5 per million output tokens. A typical exchange in this project uses a few hundred tokens total, costing fractions of a cent.
- Save chat.py.
- Run your script by running this command:
python3 chat.py
You should see Claude's text response printed first, followed by input and output token counts.
Seeing an error instead of a response?
- If you see an authentication error mentioning your API key, make sure you exported it in this terminal session. Run echo $ANTHROPIC_API_KEY to confirm it is set.
- If you see a connection error, check your internet connection and try again.
- If you see ModuleNotFoundError: No module named 'anthropic', your virtual environment may not be active. Run source .venv/bin/activate and try again.
help me debug why my chat.py script is failing when I try to call the Claude API
✔️ Awesome, I've got everything!
Great. Double check you have saved chat.py and move on to the next step.
ⓧ I'd like to double check the full code
Here is the complete chat.py file for this step:
import os
import time
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude!"}],
)
print(response.content[0].text)
print(f"Input tokens: {response.usage.input_tokens}")
print(f"Output tokens: {response.usage.output_tokens}")
You just made your first API call to Claude. Next up, you will build an interactive chat loop that remembers the entire conversation.
Build the Interactive Chat Loop with Conversation Memory
In the last step, you sent a single message to Claude and got a response back. That proves the API works, but a single exchange is not a conversation.
In this step, you will turn your one-shot script into an interactive chatbot that remembers everything you have said. You will build a loop that keeps the conversation going and tracks performance stats on every call.
In this step, get ready to:
- Add an input loop that reads user messages and exits on quit or exit.
- Track the full conversation in a messages list so Claude remembers context.
- Measure and display latency alongside token usage after each response.
Understand how conversation memory works
LLMs are stateless. Claude does not remember your previous messages between API calls.
To create the illusion of a conversation, you must send the entire conversation history with every request. Your code will maintain a messages list and append each new exchange. On every API call, you send the full list.
Why do you need to resend the full conversation every time?
This is how ChatGPT, Claude.ai, and every chat product works under the hood. The server has no session state. Your client is responsible for assembling the full context on each request.
The tradeoff: longer conversations use more input tokens per call because you are re-sending everything. You will see this reflected in the token counts printed after each response.
Build the input loop and quit handling
You are going to replace the entire contents of chat.py with a new structure. The new code wraps everything in a main() function and adds an input loop that keeps the conversation going.
- In VS Code, open chat.py.
- Select all the existing code (Cmd+A on macOS or Ctrl+A on Windows) and delete it.
- Paste in this code, which sets up the function structure and input loop:
import os
import time
import anthropic
def main():
# Create the API client and set up conversation tracking
client = anthropic.Anthropic()
model = "claude-haiku-4-5"
messages = []
print("Claude Chatbot (type 'quit' to exit)")
print("=" * 40)
# Keep asking for input until the user quits
while True:
user_input = input("\nYou: ").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit"):
print("Goodbye!")
break
What does this code do?
Everything now lives inside a main() function instead of running as a loose script. This is standard Python practice for keeping code organized.
- messages = [] creates an empty list that will hold the full conversation history.
- while True: creates an infinite loop that keeps asking for input.
- .strip() removes extra whitespace, and if not user_input: continue skips blank entries.
- Typing quit or exit breaks out of the loop and ends the program.
Add conversation memory, the API call, and timing
The loop skeleton handles input, but it does not do anything with the text yet. Now you will add the code that appends each message to the history, sends it to Claude, and measures how long the call takes.
- In chat.py, add the following code directly below the break line, at the same indentation level as the if statements above it (inside the while True loop):
# Add the user's message to the conversation history
messages.append({"role": "user", "content": user_input})
# Send the full conversation to Claude and measure response time
start_time = time.time()
response = client.messages.create(
model=model,
max_tokens=1024,
messages=messages,
)
elapsed_ms = (time.time() - start_time) * 1000
# Save Claude's reply to the history so it's included next time
assistant_text = response.content[0].text
messages.append({"role": "assistant", "content": assistant_text})
# Display the response and performance stats
print(f"\nClaude: {assistant_text}")
print("-" * 40)
print(
f"[Tokens: {response.usage.input_tokens} in / "
f"{response.usage.output_tokens} out | "
f"Latency: {elapsed_ms:.0f}ms]"
)
- After the main() function (outside and below it, with no indentation), add the entry point:
if __name__ == "__main__":
main()
What does this code do?
- messages.append({"role": "user", "content": user_input}) adds the user's message to the history before sending.
- client.messages.create(..., messages=messages) sends the FULL conversation history on every call. This is how Claude "remembers" what was said before.
- messages.append({"role": "assistant", ...}) saves Claude's reply so it appears in the next request too.
- time.time() captures a timestamp before and after the API call. The difference, multiplied by 1000, gives you latency in milliseconds.
- if __name__ == "__main__": main() ensures main() only runs when you execute the file directly.
- Save chat.py.
- Run the chatbot by entering this command in your terminal:
python3 chat.py
- You should see the header Claude Chatbot (type 'quit' to exit) followed by a You: prompt waiting for your input.
- Type a message like What is Python? and press Enter.
- After Claude responds, type a follow-up that references your first message (e.g. Can you give me an example of that?). Claude's reply should reference your earlier question, proving it remembers the context.
- Notice the token counts increasing with each turn. This confirms that more history is being sent on every request.
- Type quit to exit the chatbot.
Seeing an indentation or syntax error?
- Make sure the code below break is indented at the same level as the if statements (8 spaces or 2 tabs inside the while loop).
- Check that if __name__ == "__main__": is at the very bottom of the file with no indentation (it is outside the function).
- Verify your virtual environment is still active. You should see (.venv) at the start of your terminal prompt.
Help me fix a Python indentation error in my chat loop
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete chat.py at this point. Compare it line by line with yours.
import os
import time
import anthropic
def main():
client = anthropic.Anthropic()
model = "claude-haiku-4-5"
messages = []
print("Claude Chatbot (type 'quit' to exit)")
print("=" * 40)
while True:
user_input = input("\nYou: ").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit"):
print("Goodbye!")
break
messages.append({"role": "user", "content": user_input})
start_time = time.time()
response = client.messages.create(
model=model,
max_tokens=1024,
messages=messages,
)
elapsed_ms = (time.time() - start_time) * 1000
assistant_text = response.content[0].text
messages.append({"role": "assistant", "content": assistant_text})
print(f"\nClaude: {assistant_text}")
print("-" * 40)
print(
f"[Tokens: {response.usage.input_tokens} in / "
f"{response.usage.output_tokens} out | "
f"Latency: {elapsed_ms:.0f}ms]"
)
if __name__ == "__main__":
main()
Your chatbot now holds a full conversation with memory. Next up, you will give it a personality with a system prompt and add error handling so it does not crash if something goes wrong.
Add a System Prompt and Clean Output Formatting
Your chatbot works and remembers context, but Claude has no personality right now. Every response comes from an unconfigured model with no guidance on how to behave.
In this step, you'll give Claude a defined personality using a system prompt, confirm the output is cleanly formatted, and add error handling so the app doesn't crash on network issues or bad credentials.
In this step, get ready to:
- Add a system prompt that shapes Claude's personality.
- Verify the output formatting separates responses from stats.
- Wrap the API call in error handling for auth failures, rate limits, and connection issues.
Add a system prompt to your API call
The system parameter is a special instruction that tells Claude who it is and how to behave for the entire conversation. It is not part of the messages array.
What is a system prompt?
A system prompt sets Claude's personality and constraints without appearing as a "turn" in the conversation. It shapes how the model responds to every user message.
This is how products like ChatGPT and Claude.ai define different AI personalities using the same underlying model. You pass it as a top-level parameter on messages.create(), separate from the messages list.
- In chat.py, find the line that says:
model = "claude-haiku-4-5"
- Add this new line directly above it, so the system prompt is defined before the model:
system_prompt = "You are a helpful coding tutor. Keep answers concise."
Your code should now look like this:
system_prompt = "You are a helpful coding tutor. Keep answers concise."
model = "claude-haiku-4-5"
messages = []
- Now find the client.messages.create() call.
- Add system=system_prompt, between max_tokens=1024, and messages=messages, so the call looks like this:
response = client.messages.create(
model=model,
max_tokens=1024,
system=system_prompt,
messages=messages,
)
What does this change?
The system parameter is sent with every API call but never appears in the messages list. Claude reads it as background instructions that shape its tone and behavior.
You can change the prompt to anything you want. Try "You are a pirate who only speaks in nautical metaphors" if you want to have some fun later.
Verify your output formatting
Your code already prints a separator line (print("-" * 40)) between Claude's response text and the token stats. This keeps the conversation easy to read.
- Save chat.py and run your chatbot to confirm the system prompt works by running:
python3 chat.py
- Ask Claude a coding question like What is a for loop? and notice that the response is concise and tutor-like.
- Type quit to exit when you're done testing.
Response doesn't feel different?
Make sure system=system_prompt is inside the client.messages.create() call, not inside the messages list. The system parameter is a top-level keyword argument, not a message role.
help me debug my system prompt not working
Add error handling around the API call
Right now, if your API key is wrong or your internet drops, the app crashes with an ugly traceback. Production apps catch these errors and show helpful messages instead.
- In chat.py, find the block of code inside the while True loop that starts with start_time = time.time() and ends with the final print() statement showing tokens and latency.
- Replace that entire block (from start_time through the last stats print) with the following code that wraps it in a try/except:
try:
start_time = time.time()
response = client.messages.create(
model=model,
max_tokens=1024,
system=system_prompt,
messages=messages,
)
elapsed_ms = (time.time() - start_time) * 1000
assistant_text = response.content[0].text
messages.append({"role": "assistant", "content": assistant_text})
print(f"\nClaude: {assistant_text}")
print("-" * 40)
print(
f"[Tokens: {response.usage.input_tokens} in / "
f"{response.usage.output_tokens} out | "
f"Latency: {elapsed_ms:.0f}ms]"
)
except anthropic.AuthenticationError:
print("Error: Invalid API key. Check your ANTHROPIC_API_KEY.")
break
except anthropic.RateLimitError:
print("Error: Rate limited. Wait a moment and try again.")
except anthropic.APIConnectionError:
print("Error: Could not connect to the API. Check your internet.")
What does this code do?
- anthropic.AuthenticationError catches HTTP 401 responses (invalid or expired API key). It uses break to exit the loop because there is no point continuing without a valid key.
- anthropic.RateLimitError catches HTTP 429 responses (too many requests). It prints a message but lets the loop continue so you can try again.
- anthropic.APIConnectionError catches network failures (no internet, DNS issues). Like rate limits, it lets you retry on the next message.
- Save chat.py.
- Test the error handling by temporarily breaking your API key. Run this in your terminal:
export ANTHROPIC_API_KEY="invalid-key-test"
python3 chat.py
- Type any message. You should see the authentication error message print cleanly instead of a Python traceback.
- Restore your real key by running:
export ANTHROPIC_API_KEY="your-actual-key-here"
Still seeing a traceback?
Check that your try: line is at the same indentation level as messages.append(...) inside the while loop (8 spaces in). The code inside try should be indented one more level (12 spaces).
Also confirm each except line aligns with try at 8 spaces of indentation.
help me fix my try/except indentation in chat.py
✔️ Awesome, I've got everything!
Great. Double check you've saved chat.py and your real API key is exported.
ⓧ I'd like to double check the full code
Here is the complete chat.py file at the end of this step:
import os
import time
import anthropic
def main():
client = anthropic.Anthropic()
system_prompt = "You are a helpful coding tutor. Keep answers concise."
model = "claude-haiku-4-5"
messages = []
print("Claude Chatbot (type 'quit' to exit)")
print("=" * 40)
while True:
user_input = input("\nYou: ").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit"):
print("Goodbye!")
break
messages.append({"role": "user", "content": user_input})
try:
start_time = time.time()
response = client.messages.create(
model=model,
max_tokens=1024,
system=system_prompt,
messages=messages,
)
elapsed_ms = (time.time() - start_time) * 1000
assistant_text = response.content[0].text
messages.append({"role": "assistant", "content": assistant_text})
print(f"\nClaude: {assistant_text}")
print("-" * 40)
print(
f"[Tokens: {response.usage.input_tokens} in / "
f"{response.usage.output_tokens} out | "
f"Latency: {elapsed_ms:.0f}ms]"
)
except anthropic.AuthenticationError:
print("Error: Invalid API key. Check your ANTHROPIC_API_KEY.")
break
except anthropic.RateLimitError:
print("Error: Rate limited. Wait a moment and try again.")
except anthropic.APIConnectionError:
print("Error: Could not connect to the API. Check your internet.")
if __name__ == "__main__":
main()
Your chatbot now has a personality, clean output, and graceful error handling. This is the complete foundation for a production-quality CLI chat application. Next up, you'll take this further with a secret mission that adds configurable settings and conversation export.
Secret mission
Conversation Export and Configurable System Prompts
Your chatbot works, but what if you could change its personality without editing code? What if you could save an entire conversation to review later? In this extension, you'll make your chatbot configurable via a JSON file and add commands to export conversation logs and view session statistics.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally and only uses Anthropic's API when you actively run the chatbot. There are no background servers or running cloud resources that cost money while idle.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- claude-chatbot project folder on your Desktop (contains chat.py, config.json, .env, .venv, and any chat log files).
- Anthropic API key (stored in your Anthropic Console account).
- VS Code (installed application on your Mac).
Keep everything running
No action needed. Choose this if you plan to keep building on this chatbot or want to use your API key for other projects.
- Your project folder stays on your Desktop ready to use anytime.
- Your API key remains active but won't be charged unless you run the chatbot again.
- VS Code stays installed for future development work.
Pause - I'll come back to this later
Shut down running processes to free up your terminal session. Your files and API key stay intact for next time.
- If the chatbot is still running, type quit to exit it.
- Deactivate the virtual environment by running:
deactivate
- Close VS Code when you are done.
- Next time you return, reactivate the virtual environment with source .venv/bin/activate from inside your claude-chatbot folder.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below to clean up completely.
- Delete the project folder by running this in your terminal:
rm -rf ~/Desktop/claude-chatbot
- Disable your API key. Go to https://console.anthropic.com, navigate to API Keys, find the key you created for this project, and disable or delete it.
- Uninstall VS Code (optional). Open Finder, go to Applications, right-click Visual Studio Code, and select Move to Trash.
- For a clean VS Code uninstall, also remove its data folders by running:
rm -rf ~/Library/Application\ Support/Code
rm -rf ~/.vscode
- Unset the environment variable in your current terminal session by running:
unset ANTHROPIC_API_KEY
Nice Work!
Nice Work!
Nice work! You just built a fully functional AI chatbot that runs from your terminal, remembers your conversation history, and logs every API call.
You've learned how to:
- Set up a Python project with a virtual environment and the anthropic SDK, then securely configured an API key using environment variables.
- Built a multi-turn chatbot with conversation memory using the messages array pattern. This is the same architecture used in ChatGPT, Claude.ai, and every production chat interface.
- Added token usage logging, latency tracking, and error handling so you can monitor exactly what each API call costs and how long it takes.
- Secret Mission: Made the chatbot configurable via a JSON config file and added a /save command that exports your full conversation history to a timestamped log file.
Ready to quiz yourself?