<div align="center">
<img src="assets/hero.svg" width="820" alt="devlog โ a tiny developer journal that lives in your terminal" />
<p>
<img src="https://img.shields.io/badge/version-0.7.0-3b82f6?style=flat-square" alt="version" />
<img src="https://img.shields.io/badge/license-Apache--2.0-22c55e?style=flat-square" alt="license" />
<img src="https://img.shields.io/badge/Rust-2024_edition-f59e0b?style=flat-square&logo=rust&logoColor=white" alt="Rust 2024 edition" />
<img src="https://img.shields.io/badge/storage-SQLite-003B57?style=flat-square&logo=sqlite&logoColor=white" alt="SQLite storage" />
<img src="https://img.shields.io/badge/platform-macOS_ยท_Linux-64748b?style=flat-square" alt="platform" />
</p>
<em>Capture what you did, when you did it โ without ever leaving the shell.</em>
</div>
<img src="assets/divider.svg" width="100%" alt="" />
## Contents
- [Why devlog?](#why-devlog)
- [Features](#features)
- [Install](#install)
- [Usage](#usage)
- [How your data is stored](#how-your-data-is-stored)
- [Project layout](#project-layout)
- [Building from source](#building-from-source)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
## Why devlog?
Standups, retros, performance reviews, and "wait, what did I actually ship last
Tuesday?" all want the same thing: a timestamped trail of your work. `devlog`
gives you that with two commands and zero ceremony. It is **local-first**, has
**no network calls**, and keeps everything in a single SQLite file you own.
```console
$ devlog add "Refactored the store layer"
Added item "Refactored the store layer"!
```
That is the whole ritual. Type the note, hit enter, get back to work.
## Features
| ๐ **Frictionless capture** | One short command โ `devlog add "โฆ"` โ and the thought is saved. |
| ๐ **Full history at a glance** | `devlog list` groups every entry by day, most recent day first. |
| โ
**Track each entry's state** | Move items between `in_progress`, `done`, and `cancelled` with `devlog set-status`. |
| ๐๏ธ **Local-first SQLite** | Your journal lives in `~/.devlog/entries.sqlite`. No cloud, no account. |
| ๐ **Time-ordered UUID v7** | IDs encode creation time, so entries sort naturally by when they happened. |
| ๐ **Honest timestamps** | Stored in UTC (RFC 3339), shown in your local time when you `list`. |
| ๐ฆ **One small binary** | SQLite is bundled at build time โ nothing to install alongside it. |
| ๐ **Quiet by design** | No telemetry, no background process, no network. |
## Install
### From crates.io
```bash
cargo install d3vlog --locked
```
> The crate is published as **`d3vlog`**, but it installs a binary named **`devlog`**.
### From source
```bash
git clone https://github.com/w3lt/devlog
cd devlog
cargo install --path .
```
**Requirements:** a Rust toolchain new enough for the 2024 edition (Rust **1.85+**)
and a C compiler โ [`rusqlite`](https://crates.io/crates/rusqlite) compiles a
bundled copy of SQLite, so you do **not** need SQLite installed on your system.
## Usage
```console
$ devlog --help
A tiny developer journal for the terminal
Usage: devlog <COMMAND>
Commands:
add Add a new journal entry
list List journal entries
set-status Set status of entry
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
| `devlog add <message>` | Append a new entry, stamped with the current UTC time. | `devlog add "Cut the v0.2 release"` |
| `devlog list` | Print every entry, grouped by day (newest day first). | `devlog list` |
| `devlog set-status <id> <status>` | Set an entry's status to `in_progress`, `done`, or `cancelled`. | `devlog set-status <id> done` |
| `devlog --version` | Show the installed version. | `devlog --version` |
| `devlog --help` | Show help (works on subcommands too). | `devlog add --help` |
### A typical session
```console
$ devlog add "Ship the new auth flow"
Added item "Ship the new auth flow"!
$ devlog add "Fix flaky test in store.rs"
Added item "Fix flaky test in store.rs"!
$ devlog list
Wednesday, 2026-06-24 ยท 2 entries
[~] 09:14 Ship the new auth flow
id: 019efa5e-5f23-70b3-b4d3-f5f1643764a3
[~] 11:02 Fix flaky test in store.rs
id: 019efa5e-5f2a-7eb0-9ed7-9980495715a5
$ devlog set-status 019efa5e-5f23-70b3-b4d3-f5f1643764a3 done
Set status of item 019efa5e-5f23-70b3-b4d3-f5f1643764a3 to be Done
$ devlog list
Wednesday, 2026-06-24 ยท 2 entries
[โ] 09:14 Ship the new auth flow
id: 019efa5e-5f23-70b3-b4d3-f5f1643764a3
[~] 11:02 Fix flaky test in store.rs
id: 019efa5e-5f2a-7eb0-9ed7-9980495715a5
```
Entries are grouped under a day header โ `<weekday>, <YYYY-MM-DD> ยท <count>` โ
with the most recent day first. Within a day, each entry shows a status marker,
its local `HH:MM` time, and the message, followed by the full UUID on an
indented `id:` line:
- `[~]` โ in progress (the state every new entry starts in)
- `[โ]` โ done
- `[x]` โ cancelled
Move an entry between states with `devlog set-status <id> <status>`, passing the
full id from the `list` output and one of `in_progress`, `done`, or `cancelled`.
## How your data is stored
Everything lives in one SQLite database, created on first run:
```
~/.devlog/entries.sqlite
```
The schema is a single table:
```sql
CREATE TABLE IF NOT EXISTS devlog_entries (
id TEXT PRIMARY KEY NOT NULL,
created_at TEXT NOT NULL CHECK (datetime(created_at) IS NOT NULL),
message TEXT NOT NULL CHECK (length(trim(message)) > 0),
status TEXT NOT NULL DEFAULT 'in_progress'
CHECK (status IN ('in_progress', 'done', 'cancelled'))
);
```
| `id` | `TEXT` | UUID v7 โ time-ordered, generated per entry. |
| `created_at` | `TEXT` | UTC timestamp in RFC 3339; the `CHECK` rejects anything SQLite can't parse as a datetime. |
| `message` | `TEXT` | The note. Must be non-empty after trimming whitespace. |
| `status` | `TEXT` | Lifecycle state โ one of `in_progress` (the default for new entries), `done`, or `cancelled`. |
The schema is versioned (via SQLite's `PRAGMA user_version`), so existing
journals are migrated in place when you upgrade `devlog` โ the `status` column
was added this way.
Because it is plain SQLite, you can always inspect or back up your journal with
ordinary tools:
```bash
sqlite3 ~/.devlog/entries.sqlite "SELECT created_at, status, message FROM devlog_entries;"
```
## Project layout
```
devlog/
โโโ Cargo.toml # crate: d3vlog ยท binary: devlog
โโโ src/
โโโ main.rs # entry point โ parse args, dispatch commands
โโโ cli.rs # clap definitions for `add`, `list`, and `set-status`
โโโ store.rs # SQLite connection, schema migrations, reads & writes
โโโ data.rs # data module root
โโโ data/
โโโ entry.rs # DevLogEntry model + display formatting
โโโ status.rs # DevLogEntryStatus enum + parsing & rendering
```
The dependencies are intentionally few:
- [`clap`](https://crates.io/crates/clap) โ argument parsing (derive API)
- [`rusqlite`](https://crates.io/crates/rusqlite) โ SQLite access (bundled)
- [`chrono`](https://crates.io/crates/chrono) โ UTC timestamps
- [`uuid`](https://crates.io/crates/uuid) โ UUID v7 identifiers
## Building from source
```bash
# Run without installing
cargo run -- add "Trying devlog from a checkout"
cargo run -- list
# Optimized build
cargo build --release # binary at target/release/devlog
```
## Roadmap
Ideas under consideration โ **not yet implemented**:
- [ ] `devlog search <term>` โ filter entries by text
- [ ] `devlog rm` / `devlog edit` โ remove or amend entries
- [ ] Date filters (`--since`, `--today`, `--week`)
- [ ] Tags / projects per entry
- [ ] Export to Markdown or JSON
- [ ] A scrollable TUI view
Have a different itch? Open an issue.
## Contributing
Issues and pull requests are welcome.
```bash
git clone https://github.com/w3lt/devlog
cd devlog
cargo build
cargo run -- list
```
Please keep changes small and focused, and run `cargo fmt` and `cargo clippy`
before opening a PR.
## License
Licensed under the **Apache License, Version 2.0**. See [`LICENSE`](LICENSE) or
<https://www.apache.org/licenses/LICENSE-2.0> for the full text.
<img src="assets/divider.svg" width="100%" alt="" />
<div align="center">
<sub>Built with ๐ฆ Rust and SQLite ยท <a href="https://github.com/w3lt/devlog">github.com/w3lt/devlog</a></sub>
</div>