Skip to content

Repository files navigation

atasktodo 🐾

A cozy, self-hosted, single-user to-do app where Taski — a cute black-and-white pixel-art caretaker — decides what you should work on next, visual-novel style.

Taski greets you, quips at you, and when you ask "give me a task" she picks one using a deliberately semi-random algorithm. Sometimes she hands you something you've neglected for ages, sometimes something you already half-finished, and sometimes just pure luck of the draw. She's sometimes harsh, sometimes silly, sometimes cute — and she has a large bank of dialogue so she stays fresh.

Taski is a 16×16 pixel character with dark/light themes


Features

  • 🎭 Taski, an emotive pixel character with 9 expressions and hundreds of dialogue lines that react to everything you do.
  • Tasks + subgoals — a task is "done" when you check off all its steps.
  • 🎲 "Give me a task" — a semi-random picker with no real priority, just moods and reasons (stale, half_done, random).
  • ▶️ One active task at a time — check off steps, pause it (returns to the list with progress kept), or remove it. Every action gets a Taski response.
  • 🌗 Dark & light themes (grayscale by design; toggle any time).
  • 📱 Desktop and mobile friendly.
  • 🔒 Single-user login with a password you set.
  • 🐳 Dockerized & self-hosted, with a persistent SQLite volume.

Quick start (Docker)

docker-compose.yml pulls the prebuilt image from GHCR — no local build needed:

cp .env.example .env       # then edit .env and set ATASKTODO_PASSWORD
docker compose up -d       # pulls ghcr.io/troglobitten/atasktodo:latest

Open http://localhost:3000 and log in with the password you set.

Prefer to build from source? In docker-compose.yml, comment out the image: line and uncomment build: ., then run docker compose up -d --build.

⚠️ If you don't set ATASKTODO_PASSWORD, the default is taski — change it before exposing this anywhere.

If port 3000 is already taken (on Windows it can fall in a reserved range), change the left side of the ports: mapping in docker-compose.yml, e.g. "8080:3000", and open that port instead.

Behind HTTPS?

Set ATASKTODO_INSECURE_COOKIE=0 in .env when you serve over TLS (e.g. behind a reverse proxy). Keep it 1 for plain-HTTP LAN use so the login cookie works.

Or run the prebuilt image

Every push to main builds and publishes a container image to the GitHub Container Registry via GitHub Actions, so you can skip the build:

docker run -d --name atasktodo \
  -p 3000:3000 \
  -e ATASKTODO_PASSWORD=change-me-please \
  -e ATASKTODO_INSECURE_COOKIE=1 \
  -v atasktodo-data:/data \
  ghcr.io/troglobitten/atasktodo:latest

The GHCR package is private by default — either docker login ghcr.io first, or flip the package to public under the repo's Packages settings.


Run locally without Docker

npm install
ATASKTODO_PASSWORD=yourpassword npm start
# open http://localhost:3000

On Windows, if port 3000 is in a reserved range you can pick another:

$env:ATASKTODO_PASSWORD="yourpassword"; $env:PORT="4000"; npm start

Environment variables

Variable Default Purpose
ATASKTODO_PASSWORD taski Your login password. Change it.
ATASKTODO_SECRET generated JWT signing secret. If unset, one is generated & stored in DB.
ATASKTODO_DATA_DIR ./data Where the SQLite file lives (mount a volume in Docker).
ATASKTODO_INSECURE_COOKIE unset Set 1 for plain HTTP; 0/unset behind HTTPS.
PORT 3000 HTTP port.
HOST 0.0.0.0 Bind address.

Reworking the pixel art 🎨

All of Taski's art is plain editable text grids — no image editor needed.

  • public/assets/sprites/sprites.js — every expression is a 16×16 grid of characters you can hand-edit:
    • . = transparent, K = ink (outline/features), W = body fill, C = accent (cheeks).
    • Colours come from CSS variables, so each sprite auto-inverts for dark/light.
    • To add an expression, copy a block, rename it, redraw its 16 rows, and use the new mood name in the dialogue file.
  • public/assets/sprites/favicon.svg — the browser-tab face, as editable rects.
  • Colours live in public/css/styles.css (the --ink, --paper, --accent, --stage, --bg variables). Change them to reskin everything.
  • UI icons (check, ✕, +, star, diamond, power, sun) are hand-authored pixel SVGs in the PIXEL ICONS block of styles.css, drawn as <rect> pixels and tinted with currentColor so they follow the theme. Add one with a --i url and a .pi-yourname class, then drop <span class="pi pi-yourname"></span> in.

The font 🔤

  • The UI uses Doto, a variable pixel font, self-hosted in public/assets/fonts/ so it works offline.
  • Weight and roundness are controlled by two variables at the top of styles.css: --font-wght (default 900) and --font-rond (default 100 = fully round dots). Change those two numbers to restyle all text.
  • Fallbacks: Bitcount Single (also bundled) → the monospace stack. Swap the @font-face files or the --font stack to change typefaces entirely.

Rewriting Taski's dialogue 💬

  • public/data/dialogue.json is the entire script. Each line is { "mood": "...", "text": "..." }, grouped by situation (greeting, task_added, subgoal_checked, task_completed, task_paused, task_removed, idle, and the nested task_given by reason). mood must match a sprite name; {task} is replaced with the task title. Add as many lines as you like — the app avoids repeating the last line it used per category.

Task mood faces 🙂

Each task shows a little pixel face next to its title reflecting how long it has sat untouched:

  • 🙂 happy — touched within the last 7 days
  • 😐 neutral7 to 30 days untouched
  • 🙁 sad30+ days untouched

The thresholds live in moodStage() in public/js/app.js (just change the two day numbers). The faces are pixel-art mask icons (.pi-mood-happy/neutral/sad) in the PIXEL ICONS block of public/css/styles.css, tinted with the theme's ink colour. Hovering a card shows exactly how many days it's been idle.

Tuning how Taski picks tasks 🎲

  • server/taski.js holds the algorithm. Adjust REASON_WEIGHTS to bias her toward stale / half-done / random, or STALE_MS for what counts as "a long time." There is intentionally no urgency/priority system.

Project layout

atasktodo/
├─ server/
│  ├─ index.js      Express API + static host
│  ├─ db.js         SQLite storage
│  ├─ auth.js       single-user login (JWT cookie)
│  └─ taski.js      semi-random task-picking algorithm
├─ public/
│  ├─ index.html
│  ├─ css/styles.css
│  ├─ js/           app.js · api.js · taski.js (voice + face controller)
│  ├─ assets/sprites/  sprites.js (EDIT ME) · favicon.svg
│  └─ data/dialogue.json  (EDIT ME — Taski's whole script)
├─ Dockerfile
├─ docker-compose.yml
└─ .env.example

Data & backups

Everything lives in one SQLite file inside the atasktodo-data Docker volume (or ./data/atasktodo.sqlite locally). Back that up and you've backed up everything.

License

MIT — do whatever you like. Taski believes in you.

About

A cozy self-hosted pixel-art task manager where Taski, a cute caretaker, decides what you should do next. Dark/light themes, single-user login, Dockerized.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages