Building a SQLite CLI in less than an hour without looking at any code at all
It's never been a better time to be a lazy developer.
Note: Starting a new series to document small tools that I whip up with vibe coding in my free time. Coding is fun again, wheeeeeeeeeeee.
Total time: 54 minutes.
Total cost: $4.34
Artifacts:
I'm using sqlite a lot these days. Mostly because I'm lazy and don't want to implement an actual database for my startup. But I justify that to myself by saying that we're pre PMF (true) and we need to move fast (true) and implementing an actual database will slow us down (ehhhhhhhhhhhh probably true).
So sqlite. It's easy. No devops. Just npm install.
But man, I hate basically all of the sqlite clients out there. None of them really just work the way I want. The big thing that I hate is that I am constantly tabbing over to claude to ask it to write sql for me because I never really bothered to learn the actual syntax. I'm, like, really really lazy. I don't want to tab. I just want to just be able to do that directly in terminal, and that too with all of the context with my existing tables.
For example, if I go "give me all of the names of my customers" I want the cli to already know that I have a table called users and that the users table has two fields, first and last. That way when it spits out sql, it should give me select first, last from users. I believe Karpathy calls this 'context engineering'.
O, I also want tab complete. Why do so few sqlite3 cli tools have tab complete?
Figured this would be virtually a one shot with claude code.
Initial prompt:
Create a sqlite3 cli tool using node. The tool should be a thin wrapper over the existing sqlite3 cli. For example, .table, .headers on, etc. should all work as usual, as should sql queries. The tool should have the following additional features:
- the tool should support tab complete. If I hit tab, I should see a list of available options given my current typing. For example, if I hit tab after "se" I should see `select` as an option. Similarly, if I hit tab after "select * from " I should see a list of available tables
- if the user types ".model <string>", the system should store the model string. The default model string should be "claude-sonnet-4-20250514". If the user types ".model" it should output the current model value.
- if the user types ".anthropic-key <string>", the system should store the anthropic key. If the user types ".anthropic-key" it should output the current anthropic key value
- if the user types ".ai <string>", the system should make a call to anthropic using the model string and openrouter-key configured above. The response from the AI should replace the value in the cli input, as if the user had typed it, but before submission. The system prompt for this query to anthropic should require that the model produce valid SQL. It should also contain key database metadata, like available tables and their associated schemas.
This basically provided the core framework of the tool. I never really moved away from the initial scaffolding. I also didn't, like, look at the code. This is vibe coding, what you want me to review things?
Anyway, fired up the app…and the tab complete doesn't work. I am useless without being able to jam the tab button with my pinky as fast as possible.
Next prompt to chat:
The tab complete does not behave as expected. I can correctly tab the first word, but when I try to tab the next one it deletes the previous. For example,
SEL <tab> --> SELECT
SELECT * FR <tab> --> FROM (the SELECT * is removed)
While claude code is chewing on things, I go warm up and eat two taquitos while doom scrolling on reddit. This takes about fifteen minutes, mostly because of the doom scrolling. At some point I snap out of it and come back over to the laptop, where claude code has been patiently waiting for my return.
And it fixed the issue.
On to creating a table! I went to the claude ui to ask it how to create a table because as mentioned I do not know how to sql and I am very lazy.
Thanks claude!
Then I went back to my little app and tried the first CREATE TABLE part and bam! Everything broke. The app couldn't handle multi line inputs. Come on claude code you should do better than that. Next prompt:
If i try to copy paste the following into the sqlite cli, it fails:
sqlite> CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER,
city TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Error: in prepare, incomplete input (1)
Error: in prepare, near "id": syntax error (1)
Error: in prepare, near "name": syntax error (1)
Error: in prepare, near "email": syntax error (1)
Error: in prepare, near "age": syntax error (1)
Error: in prepare, near "city": syntax error (1)
Error: in prepare, near "created_at": syntax error (1)
sqlite> Error: in prepare, near ")": syntax error (1)
As you can see, I am brilliant at prompt engineering, using the tried and true message of “keep spamming error messages until it works”. Though it did not require a lot of spamming — claude code again one shot this problem and I now had user data in my database.
At this point I had basically everything I wanted. I asked for some stylistic changes
Normally in sqlite you have to type .headers on for the table headers to appear. I want them to always appear.
And some nicer JSON outputs
Add the ability to pretty print JSON.
And some improvement to the docs
Update the readme docs to indicate the proper installation method through npm (npm i sqlite3-ai-cli) and the bin usage
And some help with npm publishing
Modify this package so that when it is published and installed, I can run sqlite3-ai as a command line arg from the shell directly
And that's it. Published the first version to npm, downloaded it, ran it. Seemed to work end to end.
Currently the cli does not track state or previous commands for the AI context, but I think that's an easy feature to add. It also does not support the other model providers, but are you really going to be using anything other than claude for this sort of thing?
A few misc thoughts.
Overall, I’m pretty happy with the outcome — immediately useful in my actual day job.
Continually impressed with how easy it is to just build fun little tools these days. I didn’t look at the actual code output a single time. I mostly just whined at the AI.
The repo generated ~1400 lines of code. That’s pretty small, well within the range of the context window. But also there’s nothing here that was meant to blow your socks off, its a sqlite cli. I’m more interested in how far you can go just by offloading things to AI interfaces. The answer: pretty damn far.
To that point, context is everything. Intelligently passing context to the AI model is probably 80% of what most AI startups are doing these days. It sometimes really is as simple as ‘load the users data and then send that to the AI model first’.
I cannot imagine hiring someone for any role who isn’t at least passingly familiar with these tools. It’s so rapidly become such a table stakes thing, you don’t even have to look at the code! Everyone should be able to build scripts to automate parts of their jobs, whether that’s a sales person writing a webscraper to put together leads or a designer building interactive mockups with a react app.
Try out the finished result by running:
npm install sqlite3-ai-cli -g
sqlite3-ai test.db
sqlite> .shell echo "hello world"
Not to mention, survivor bias. SQLlite is really, really good. That is why it is still standing so long after it started. You would waste your time trying to beat it if all you need is a simple embedded database. You need to avoid reinventing wheels. College should teach that, but of course college generally is in business to have you learn from building old wheels, leaving many people with the wrong lesson on what to do in the real world.
But using Claude to wrap it? Absolutely the right choice.
the era of "personal software"! Or maybe better said, single use software...
I had a similar test a year ago, seeing if Claude or OpenAI could figure out how to integrate `vim-dadbod` with Athena query results, and it got about halfway there with 2 hours of constant prodding. Now I'd be surprised if I couldn't do the entire thing in an hour's time, with how much more effective Claude code has gotten