
TL;DR: I built Command Book, a native macOS app that gives your long-running terminal commands a permanent home. Free to download at commandbookapp.com.
I’ve been a terminal power user for over 20 years. And I’m done using terminal tabs as a process manager. Here’s why.
It’s a familiar tale. You sit down to work in the morning and you have to get a host of apps up and running before you can start coding. Maybe your terminal looks a bit like this.
------------------------- Terminal -----------------------------
| python | [python] | python | python | docker | tail | zsh |
-----------------------------------------------------------------
| $ python src/app.py --reload |
| Flask app starting up... listening on http://127.0.0.1:5000 |
| ... |
It’s pretty common! And until recently, that’s how I started my day.
Each of these commands requires that I remember which working directory to start in (the daemon runs somewhere else than the flask app for example). Getting them up and running is a bit tedious. Certainly doable, but tedious.
You’re working through the day, and you realize some part of your app isn’t working. It crashed. Why? You had --reload set and it dutifully reloaded on file changes. But the code was half-ready. You or Claude wrote the import statement, import new_feature, but you haven’t yet created that module. Or maybe it reloaded while you were mid-function having written def scan_files( and you get an unmatched brace. Reload fails and you’re hunting through terminal tabs for which part stopped working and needs to be restarted.
Terminals are great for interactive work, exploration, quick commands. But they are terrible as a process manager. What I wanted was a command/process manager for long running commands currently living in my terminal.
I wanted them to be reproducible, instant, reliable and auto restarting if code changes, not just reloading unless the code gets out of sync.
I wanted a terminal companion.
I didn’t find this app. So I built it. After 10 years of running Talk Python, Talk Python Training, and a handful of other production apps, I knew exactly what I needed, and what was missing.
Introducing Command Book: A native macOS app built with SwiftUI – no Electron, no Chromium, just a fast, lightweight experience that feels at home on your Mac.
Command Book is for people like me: Developers juggling a web server, a database, a couple of background workers, and a log tail every day. Data scientists who kick off long training runs and need to know when they crash. Anyone who’s tired of Electron apps chewing through 500 MB of RAM to do something a 21 MB native app handles just fine.

——— Save a command once, run it forever ———
Create a command, specify a working directory, env vars, pre-commands like git pull, and the main command to run. For example, to work on talkpython.fm I have this command:
Command: python talk-python/web_app.py --reload
Working directory: ~/github/talk-python-web/
Pre-command: git pull
——— Auto-restart on crash ———
Command Book can go into Honey Badger mode to keep you productive. You can check a box to always restart the command when it crashes. This is perfect for that dev-server --reload command, except when something goes wrong like an unmatched brace then reload becomes crashes. Now Command Book will restart it until the code is fixed.
It is configurable with a delay so it doesn’t go too Honey Badger.
——— Keyboard-first with ⌘K ———
Command Book is a GUI, but one built for developers. So it’s very keyboard focused (it also comes with a CLI interface). Many actions are keyboard first. This is most evident with our command palette. Pressing ⌘K, you can search, run, and create saved or ad-hoc commands.

——— URL detection ———
Another papercut Command Book looks to solve is getting back to your dev server. Did you click the link when the server started but absent-mindedly close the browser an hour later? Now the terminal output has scrolled back 1,000 lines and you have to hunt for the URL to get back?
This is a common problem with Flask, Django, Node, Jupyter Notebooks, and many more.
So when a command runs, Command Book grabs the first few urls and keeps them accessible at the top of the command output. That way, you can always get back to the running app regardless of how much output has streamed by.
——— CLI integration ———

Command Book’s GUI lets you configure commands with precision: working directories, pre-commands, environment variables, auto-restart behavior, and more. The CLI brings all of that to your terminal with zero extra setup.
Instead of remembering the full incantation, you just run commandbook run talk-python-dev and it does exactly what the GUI would do - same directory, same env vars, same everything.
$ commandbook run talk-python-dev
Command Book comes with a free personal license as well as a paid, pro edition. You should definitely be able to see if the app is useful for you with the personal edition. To get started, just download Command Book for free at:
If you do decide to upgrade, it’s just $14.99 one-time. No subscription. No account. No tracking.
There’s no VC runway behind this app, no enterprise upsell waiting in the wings, and no tracker phoning home. Just a tool that gets better because users support it directly. Zero enshittification. If Command Book saves you from rebuilding your terminal setup even once, it’s paid for itself.
I’d really appreciate it if you gave it a look, shared it with your team, and sent me feedback. Feel free to email me with feedback. Consider signing up for announcements (the newsletter) too.
The app is still getting final touches. So community input can truly drive the direction of what comes next. For example, a Windows version is under consideration and more features planned.
I’ve been bewildered by the wide range of experiences that software developers observe when working with AI. This has led many of us to outright reject AI for coding.
There are reasons for this variance.
Skeptics want to lightly dip their foot in the water to see what it’s about. This manifests as using the cheapest or free models and “just send it” styles of programming. Then they lament that what comes out is not what they would have built nor is it what they wanted.
Meanwhile, professional developers adopting agentic AI for coding operate differently. They see spending $100+/mo on AI tools as a great bargain. They spend hours planning and refining and decomposing a problem before they send a single request to their fancy AIs. Code carefully matching their request is often the outcome.
Is it a surprise they are getting different results? I’ve seen both styles and it’s no surprise to me.
These two experiences often get lumped under the same term: Vibe Coding.
They are not the same.
Today I ran across an essay by Addy Osmani, “Agentic Engineering.”
Addy really nails this difference and spells out a term that I’m keen to adopt: Agentic Engineering.
Vibe coding means going with the vibes and not reviewing the code. That’s the defining characteristic. You prompt, you accept, you run it, you see if it works. If it doesn’t, you paste the error back and try again. You keep prompting. The human is a prompt DJ, not an engineer.
…
Here’s the thing: a lot of experienced engineers are now getting massive productivity gains from AI - 2x, 5x, sometimes more - while maintaining code quality. But the way they work looks nothing like vibe coding. They’re writing specs before prompting. They’re reviewing every diff. They’re running test suites. They’re treating the AI like a fast but unreliable junior developer who needs constant oversight. I’ve personally liked “AI-assisted engineering” and have talked about how this describes that end of the spectrum where the human remains in the loop.
…
It draws a clean line. Vibe coding = YOLO. Agentic engineering = AI does the implementation, human owns the architecture, quality, and correctness. The terminology itself enforces the distinction.
Give the full article a read.
That last quote is important. Many devs dread AI because they don’t want to be a full time reviewer for marginal code. But if “AI does the implementation, human owns the architecture, quality, and correctness”, that sounds like senior developers’ job descriptions to me.
It’s something to think about.
Explore SQL Server 2025's new SQL concatenate string with the double pipe operator for efficient data handling.
The post SQL Concatenate String using Double Pipe (||) Operator in SQL Server 2025 appeared first on MSSQLTips.com.
Sometimes applications need to remove data without actually losing it. Soft delete keeps rows in the database while making them invisible to normal application access. This is especially valuable when exposing a database to an AI agent through an MCP server like SQL MCP Server, where safety and reversibility matter.
Learn about SQL MCP Server
Filtering on an IsDeleted column in every query is fragile. One missed filter exposes your data. Row-Level Security enforces visibility rules inside the database so application code cannot bypass them. Let’s take a look.
Working demo: https://gist.github.com
A Todos table where soft delete is enforced at the database layer.
This example uses a SQL login for simplicity. In Azure SQL, you would typically use a managed identity. The pattern is the same.
CREATE LOGIN TodoDbUser WITH PASSWORD = 'Long@12345';
GO
CREATE USER TodoDbUser FOR LOGIN TodoDbUser;
GO
CREATE TABLE dbo.Todos
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Title NVARCHAR(200) NOT NULL,
State NVARCHAR(20) NOT NULL DEFAULT 'pending',
IsDeleted BIT NOT NULL DEFAULT 0
);
GO
INSERT INTO dbo.Todos (Title, State)
VALUES
('Buy groceries', 'pending'),
('Walk the dog', 'completed'),
('Finish report', 'in-progress'),
('Call mom', 'pending'),
('Clean the house', 'completed');
GO
Instead of issuing a DELETE, the application calls a stored procedure that marks the row as deleted.
CREATE PROCEDURE dbo.DeleteTodo
@Id INT
AS
UPDATE dbo.Todos
SET IsDeleted = 1
WHERE Id = @Id;
GO
This stored procedure is the only delete mechanism exposed to the application.
The application can select and update rows, but it cannot delete them directly.
GRANT SELECT, INSERT, UPDATE ON dbo.Todos TO TodoDbUser;
GRANT EXECUTE ON dbo.DeleteTodo TO TodoDbUser;
GO
At this point, the application still sees deleted rows. Let’s fix that.
Row-Level Security controls which rows are visible to which users. It applies to SELECT, UPDATE, and DELETE. Filtering happens before the statement executes.
CREATE FUNCTION dbo.fn_SoftDeletePredicate(@IsDeleted BIT)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT 1 AS fn_result
WHERE
(
DATABASE_PRINCIPAL_ID() = DATABASE_PRINCIPAL_ID('TodoDbUser')
AND @IsDeleted = 0
)
OR DATABASE_PRINCIPAL_ID() <> DATABASE_PRINCIPAL_ID('TodoDbUser');
GO
TodoDbUser sees only rows where IsDeleted = 0CREATE SECURITY POLICY dbo.TodosFilterPolicy
ADD FILTER PREDICATE dbo.fn_SoftDeletePredicate(IsDeleted)
ON dbo.Todos WITH (STATE = ON);
GO
From this point forward, deleted rows are invisible to the application user.
SELECT * FROM dbo.Todos;
EXECUTE AS USER = 'TodoDbUser';
EXEC dbo.DeleteTodo @Id = 2;
REVERT;
EXECUTE AS USER = 'TodoDbUser';
SELECT * FROM dbo.Todos;
REVERT;
The deleted row is hidden from the application user but remains visible to admins.
EXECUTE AS USER = 'TodoDbUser';
UPDATE dbo.Todos SET IsDeleted = 0 WHERE Id = 123;
REVERT;
The application user cannot undelete a row. Row-Level Security hides deleted rows before the UPDATE executes, so the statement matches zero rows.
If you prefer explicit enforcement rather than silent filtering, add a block predicate.
ALTER SECURITY POLICY dbo.TodosFilterPolicy
ADD BLOCK PREDICATE dbo.fn_SoftDeletePredicate(IsDeleted)
ON dbo.Todos AFTER UPDATE;
To use a managed identity instead of a SQL login:
CREATE USER [my-container-app] FROM EXTERNAL PROVIDER;
Reference that user in the predicate function. Everything else stays the same.
The post Enable Soft Delete in Azure SQL appeared first on Azure SQL Devs’ Corner.