Enter the flint state.
An open-source, local-first, keyboard-driven, plugin-extensible desktop timer. The Obsidian of timers.
Grab the latest installer from the releases page:
| Platform | File |
|---|---|
| Windows (MSI) | Flint_x64_en-US.msi |
| Windows (EXE) | Flint_x64-setup.exe |
| macOS (Apple Silicon) | Flint_aarch64.dmg |
| macOS (Intel) | Flint_x64.dmg |
Binaries are unsigned for v0.1 — macOS: right-click → Open. Windows: SmartScreen → More info → Run anyway. Linux builds and signed binaries will land in a later release.
Flint is not a timer app — it is a timer engine. The core ships a small set of primitives (hooks, commands, render specs, presets, tags, plugin storage, interval authoring, prompts, stats) and plugins compose any workflow on top. Data lives in plain JSON files under ~/.flint/; there is no cloud, no account, no telemetry, no network call. Every action is reachable from the command palette or a direct shortcut — the mouse is optional.
- Timer modes — Pomodoro, Stopwatch, Countdown. All three are plugins. Community timer modes ship the same way.
- Plugin system — Before- and after-hooks across the full session lifecycle, searchable commands, declarative render specs, per-plugin storage, preset packs, stats queries, and a prompt primitive. Plugins run in a sandbox: no DOM access, no
fetch, no Tauri internals. - Command palette —
Ctrl+Popens a fuzzy-search palette for every action in the app. Plugin commands show up next to core ones. - Session presets — One-keystroke start into saved configurations. Overrides are session-scoped and never touch
config.toml. - Tags — Per-session tags with autocomplete against every tag you have ever used. Plugin hooks can rewrite or veto tag changes.
- Stats dashboard — Today, last-seven-days, last-month, yearly heatmap, lifetime totals. Backed by a rebuildable SQLite cache over the session JSON.
- System tray — Start, switch mode, stop, quit from the tray menu without touching the main window.
- Floating overlay pill — A fixed 336×64 window pinned to any screen corner that shows the current interval and elapsed time.
- Keyboard-driven — Fixed core shortcuts (
Space,Enter,Escape) plus app-wideCtrl+P,Ctrl+B,Ctrl+Shift+O,Ctrl+,,Ctrl+Q, and number keys for mode / preset selection. - Local-first — Session files are the source of truth. Everything else is a cache that can be rebuilt from disk.
Install from the Download table above, launch Flint, and press Space to start your first session. The first launch creates ~/.flint/ — that folder is your data.
| Key | Action |
|---|---|
Space |
Start / pause / resume |
Enter |
Mark a question (while running or paused) |
Escape |
Confirm stop, or close a modal |
| Key | Action |
|---|---|
Ctrl+P |
Toggle command palette |
Ctrl+B |
Toggle sidebar |
Ctrl+Shift+O |
Toggle floating overlay |
Ctrl+, |
Open settings |
Ctrl+T |
Open tag input |
Ctrl+Q |
Quit |
Ctrl+1..9 |
Switch timer mode (when idle) |
1..4 |
Load pinned preset (when idle, no modifier) |
Every shortcut is also a command in the palette. Hotkeys are fixed in v0.1.
A Flint plugin is a JavaScript module plus a manifest.json. Plugins receive a flint API object and run in a sandbox with window, document, fetch, localStorage, and Tauri internals shadowed to undefined. From inside that sandbox a plugin can:
- Hook the session lifecycle. Before-hooks (
flint.hook) can veto or mutatesession:start,session:pause,session:resume,session:stop,session:cancel,question:mark,interval:next,preset:load,command:execute,notification:show,tag:add, andtag:remove. After-hooks (flint.on) observe every event, including engine-firedinterval:start,interval:end,session:complete,app:ready, andapp:quit. - Register commands.
flint.registerCommand({ id, name, callback })makes any action searchable fromCtrl+P, with optional icon, category, and informational hotkey badge. - Author intervals.
flint.setFirstIntervalandflint.setNextIntervalpush interval directives into the Rust engine's pending slots. The Pomodoro plugin drives its own focus / break / long-break math through these APIs; community timer modes ship the same way by declaring"timer_mode": truein the manifest. - Render UI declaratively.
flint.registerView(slot, renderFn)returns a JSON render spec the host interprets. Supported widgets:container,text,stat-row,bar-chart,line-chart,heatmap,table,button. Plugins describe what to render, the host renders it — no JSX, no DOM. - Prompt the user.
flint.prompt({ title, body, accept, decline, timeout? })blocks on a centered dialog and resolves with"accepted","declined", or"dismissed". Prompts queue; at most one is visible at a time. - Show notifications.
flint.showNotification(msg, { duration })toasts with duration clamped to 1–15 seconds. At most three are visible; a per-plugin dedup window prevents spam. - Read data.
flint.getSessions()for raw session JSON,flint.stats.today() / range(scope) / heatmap(days) / lifetime()for aggregates straight out of the SQLite cache. - Ship preset packs.
flint.presets.list / save / load / deletelets a plugin seed starter presets on first load. - Persist per-plugin data.
flint.storage.{get,set,delete}writes atomic JSON files under~/.flint/plugins/{id}/data/. 5 MB cap per key.
pomodoro— configurable focus / break / long-break cycle, drives its own intervals throughsetFirstInterval/setNextInterval.stopwatch— open-ended timer with lap marking viaEnter.countdown— fixed-length countdown with configurable duration.session-log— sidebar tab listing completed sessions with detail view and delete support.stats— sidebar tab rendering the stats dashboard (today / range / heatmap / lifetime).
~/.flint/plugins/hello-flint/manifest.json:
{
"id": "hello-flint",
"name": "Hello Flint",
"version": "1.0.0",
"description": "A minimal plugin that registers a command and renders a sidebar view.",
"author": "you",
"type": "community",
"entry": "index.js",
"ui_slots": ["sidebar-tab"],
"events": ["session:complete"],
"config_schema": {}
}~/.flint/plugins/hello-flint/index.js:
let sessionsToday = 0;
flint.on("session:complete", () => {
sessionsToday += 1;
});
flint.registerCommand({
id: "hello-flint:reset",
name: "Hello Flint: reset counter",
category: "hello",
callback: () => { sessionsToday = 0; },
});
flint.registerView("sidebar-tab", () => ({
type: "container",
direction: "column",
gap: 12,
children: [
{ type: "text", value: "HELLO FLINT", variant: "title" },
{ type: "stat-row", label: "Sessions today", value: String(sessionsToday) },
],
}));Drop the folder into ~/.flint/plugins/, restart Flint, and enable the plugin in Settings → Plugins.
For the complete API surface — every hook event, every render spec widget, interval authoring patterns, sandbox details, and the full plugin developer guide — see CLAUDE.md.
Global configuration lives at ~/.flint/config.toml and is human-editable. The file is reloaded on app start.
[core]
default_mode = "pomodoro" # which timer mode the app opens in
auto_finalize_on_quit = true # finalize the running session on quit rather than dropping it
[pomodoro]
focus_duration = 25.0 # minutes
break_duration = 5.0
long_break_duration = 15.0
cycles_before_long = 4
auto_start_breaks = true
[countdown]
duration = 10.0 # minutes
[overlay]
corner = "top-right" # top-left, top-right, bottom-left, bottom-right
opacity = 0.95
[plugins]
# Plugin enable / disable lives here — managed from Settings → Plugins.Plugin-specific config schemas are authored in each plugin's manifest.json under config_schema and are rendered automatically in Settings → Plugins. Preset overrides are session-scoped and never persist back to this file.
Flint is local-first, not cloud-first. It does not make network calls, it does not collect telemetry, it does not phone home. Every byte of your data lives on your machine under ~/.flint/:
~/.flint/
├── sessions/ # one JSON file per completed session — the source of truth
├── presets/ # saved session configurations, one JSON per preset
├── plugins/ # community plugins, one folder per plugin, with /data for plugin storage
├── config.toml # global configuration
├── cache.db # SQLite read cache — rebuildable, safe to delete
├── recovery.json # active-session auto-save, deleted on clean end
└── state.json # app-level UI state (first-close toast shown, etc.)
Session files are plain JSON: open them in any editor, grep them with any tool, back them up by copying the folder. The SQLite cache is an index for stats queries and can be rebuilt at any time via the command palette (core:rebuild-cache).
No account. No login. No sync. Period.
Prerequisites:
- Rust (stable, 1.77+)
- Node.js 20+
- npm (bundled with Node)
- Platform-specific Tauri prerequisites — see the Tauri setup guide
Clone and install:
git clone https://github.com/techlogist1/flint.git
cd flint
npm installRun the development build:
cargo tauri devProduce release binaries:
cargo tauri buildFrontend-only dev server: npx vite. Typecheck: npx tsc --noEmit. Rust check: cargo check. Rust tests: cargo test. Format Rust: cargo fmt.
- Tauri 2.0 — Rust backend and native webview, ~5–8 MB binary
- React 18 + TypeScript (strict, no
any) for the frontend - Tailwind CSS + JetBrains Mono (bundled locally) for the terminal aesthetic
- Rust for the timer engine, session storage, plugin loader, and SQLite cache
- SQLite (via
rusqlite, bundled) as the rebuildable read cache - Recharts for plugin-authored charts in render specs
- Vite for frontend bundling and dev server
See CONTRIBUTING.md for dev setup, code style, and the PR process.
MIT.
Built by Lokavya.
