thcThought Control

Why it's like this

The few ideas behind thc. Knowing them makes the rest obvious.

thc is a place to put things down: a thought, a todo, a date, a reminder. It’s built for one person, working with a few agents, from a terminal. These are the ideas behind it.

A notebook in a terminal

Most note apps ask you to decide where something goes before you’ve finished thinking it. thc doesn’t. You open today’s page and write. Paragraphs are paragraphs, a line starting [ ] is a task, and due:fri is a date. You never fill in a form, pick a database or open a dialog. The terminal is where you already are, so the notebook lives there too.

The CLI is the interface

Every behaviour lives in the command line. The TUI is a comfortable way to drive it, and anything you can do there, you can do with thc and a few words. Interfaces that grow features only they can reach end up bending the data to fit; keeping one place where behaviour lives keeps the data honest, and means a script can do whatever you can.

Capture first, organise later (or never)

Everything lands in today’s journal unless you say otherwise. That’s a feature: the day is the natural first place for a thought. Later you can link it to a page, move it, give it a date, or leave it where it is. Today pulls what matters back in front of you, wherever it lives.

Plain text, with structure underneath

What you see is text you could open in any editor. Underneath, each paragraph, task and list item is a note with its own identity, history and fields. That’s how thc can remind you of one task, show what an agent changed in one paragraph, or undo one edit, while the page still reads like a page.

The inline syntax (Call dentist due:fri #health !high) is read once, when you write it, into typed fields. Stored text is never re-parsed to work out what something is.

One primitive

Everything is a node. A page, a journal day, a todo, a reminder and a tag are all nodes used in a particular way. “Task” isn’t a type, just a status any note can have. “Reminder” isn’t a type, just an alert attached to a note. Fewer concepts, fewer edge cases, for people and agents alike.

The fields are the ones every serious tool converged on: scheduled (when you plan to start) separate from due (when it must be done), alerts as their own objects, and repeats that advance the same item while keeping a record of each occurrence.

Nothing is ever overwritten

The source of truth is an append-only log: one JSON line per change. Nothing is edited in place, so undo always works, history is free, and “what did this look like last Tuesday?” is a flag (--as-of). A mistake, yours or an agent’s, is always recoverable. The fast local database is only a view of that log: delete it and thc rebuild makes it again.

Agents are collaborators you review

Claude, Codex or a script uses the exact same commands you do, with what they need to be reliable: --json everywhere, stable exit codes, --dry-run, idempotent creates, short random ids that never collide, and writes that are validated before they’re accepted. Every change is signed with who made it. You see what an agent did in Review, accept it with one key, or undo it with another. Agents never resolve your conflicts or accept their own work.

Local-first, yours forever

There’s no server and no account. Your notes are a folder: sync it with iCloud, Dropbox or Syncthing. Each device writes only its own files, so syncing never makes “conflicted copies”. Devices merge the same way every time: the newest change wins per field, and if two devices edit the same text, both versions are kept and flagged rather than one being lost.

The log is plain JSONL you can grep, jq, or rebuild from in fifty lines of any language. The Markdown export is regenerated as you go, and Obsidian can open it. If thc disappeared tomorrow, your notes would still be complete and readable.

Fast enough to disappear

Opening a day, typing, searching: all of it takes milliseconds, over SSH too. A query takes about as long as the OS needs to start a process, so agents can call it in loops and people never wait. That’s why it’s one Rust binary on SQLite, with no runtime to start.

Small and boring

The boring, proven tool wins: SQLite over the newer database, a JSONL log over a CRDT framework, one binary over a fleet of services. Complexity gets in only when it removes more than it adds.

Calm by design

There’s one accent colour, ember, and it means “here”: your cursor, your focus, the one thing to look at. Every other colour has exactly one meaning: overdue is red, today is amber, done is green, an agent is blue, a conflict is violet. Nothing blinks for attention. The footer shows only the few keys that work right now.