Task Master
  • TypeScript 69.3%
  • Svelte 18.1%
  • CSS 7.5%
  • Shell 4.7%
  • Swift 0.1%
Find a file
2026-10-31 15:54:39 +02:00
.forgejo/workflows Bind the deploy runtime image built on the sx toolchain at af8455c3 2026-10-28 20:04:17 +02:00
deploy/runtime Bind the deploy runtime image built on the sx toolchain at af8455c3 2026-10-28 20:04:17 +02:00
docs A stream that names a run opens only on that run's own task or session, so a task's stream refuses a session's run 2026-10-31 15:11:29 +02:00
runner user_version is declared as every function is, and the matches on what a run belongs to end at their arms 2026-10-31 14:30:35 +02:00
scripts The NAS deploy backs up the live database with sak backup before it starts the new image 2026-10-27 14:24:33 +02:00
src A stream that names a run opens only on that run's own task or session, so a task's stream refuses a session's run 2026-10-31 15:11:29 +02:00
tests A stream that names a run opens only on that run's own task or session, so a task's stream refuses a session's run 2026-10-31 15:11:29 +02:00
web A stream document whose base64 does not decode is a row with no Open, and the rows beside it keep theirs 2026-10-31 09:00:20 +02:00
.dockerignore Define the deploy runtime and prove it before shipping 2026-08-04 20:12:31 +03:00
.gitignore sak and sak-runner build into dist/ 2026-10-14 09:46:31 +03:00
docker-compose.yml S2 — The install command and the service slots (C2, E1) 2026-10-14 20:55:28 +03:00
Dockerfile S2m — sak serves the macOS sak-runner 2026-10-15 19:35:08 +03:00
README.md release: 0.3.13 2026-10-31 01:15:33 +02:00

sak

sak is a task tracker for agents and the people working with them. One binary serves an HTTP API and the single-page app built on top of it, and keeps everything in one SQLite file: projects hold tasks; a task carries text, typed attributes, labels, comments and relations to other tasks. Every API request requires a bearer token minted by the same binary; the SPA assets are served without authentication, and the server listens on 127.0.0.1 unless --bind says otherwise.

Build from source

You need:

  • the sx compiler on PATH,
  • SX_STDLIB_PATH pointing at an sx standard library whose vendored SQLite is compiled with FTS5 — task search is an FTS5 index,
  • Node.js 26.3+ and npm, for the SPA.
export SX_STDLIB_PATH=/path/to/sx/library
cd web && npm ci && npm run build && cd ..
sx build src/main.sx -o dist/sak

npm run build type-checks the SPA, runs its unit tests and writes web/dist/, which is what sak serve hands to the browser. web/dist/ is not tracked, so a checkout serves no SPA until that build runs. The SPA server test (tests/step6_spa.sx) and every Playwright suite run vite build themselves before they serve it. A Playwright run serves sak on a free loopback port it picks, or on SAK_E2E_PORT when that is set, so runs on one host do not share a port. npm run e2e:perf measures frame rate on the machine's GPU. npm run e2e:runner also starts dist/sak-runner (see runner/README.md).

Test

bash tests/run.sh

builds every tests/*.sx with sx build, as many at once as the host has cores, then runs the builds from the repository root four at a time and prints each one's result and time, the longest first. It exits 1 when a test does not build, exits non-zero, prints a FAIL line or ends on a line other than one ending in OK, and prints that test's log. Given test files, it runs only those; given none, every one but tests/step9_release.sx, which checks the archives the release script writes. It takes the compiler from $SX when that is set, and PATH otherwise, and SX_STDLIB_PATH as a build does; SAK_TEST_BUILDS and SAK_TEST_JOBS set how many build and how many run at once. The tests that serve sak or start a runner take dist/sak and dist/sak-runner, so build those first.

Run

dist/sak token create --db sak.db --name laptop
dist/sak serve --db sak.db

token create prints one token — sak_ followed by 64 hex characters — and that printed line is the only copy: the database stores its SHA-256. Mint one per client. Once signed in, the SPA's Tokens page, under the ⋯ menu, lists, creates and revokes tokens.

serve binds --bind (default 127.0.0.1) on --port (default 4345) and serves the SPA from --assets (default web/dist). --bind takes a numeric address — 0.0.0.0 and :: reach the whole network, a LAN address one interface — and anything else exits 2. --threads (default 16) sets how many requests are served at once, each on a SQLite connection of its own. The first line serve logs summarizes what it is running: version and build commit, bound address, database, assets, log settings and worker count. Open http://127.0.0.1:4345 and paste a token to get in. Reach the API with the same token:

curl -H "Authorization: Bearer sak_…" http://127.0.0.1:4345/api/health

Every request then leaves one line on stderr — the UTC instant, method, path, status, response bytes and duration, plus the cause on a 4xx or 5xx:

INFO:  2026-08-02T09:14:22Z GET /api/projects -> 200 412b 1ms
ERROR: 2026-08-02T09:14:21Z GET /api/tasks/8123 -> 404 21b 0ms get_task: StoreErr.NotFound

--log-level chooses how much of that is written: quiet keeps the refusals, requests (the default) adds a line per served request, debug adds the request size and the response content type to every line. The startup line is written at every level, and nothing is written to a file — redirect stderr to keep the lines. The last --log-ring lines (1000 by default, at most 100000) also stay in memory, where GET /api/logs serves them back newest first; docs/api.md documents that route.

Each command creates the database if it is missing.

Data

Everything lives in the one SQLite file named by --db. Migrations run when that file is opened, so upgrading is: stop the server, swap the binary, start it again. Back up by copying the file with the server stopped; restore by putting the copy back.

Run with Docker

Build the release archives on the host, then build and start the image:

bash scripts/release.sh
docker build -t git.swipelab.com/sx/sak:latest .
docker compose up -d

The compose recipe publishes port 4345, keeps the database in the sak-data volume, and restarts the container unless it is stopped. Create a token inside the container:

docker exec sak sak token create --db /data/sak.db --name laptop

Back up the database and its uploaded files while sak serves them:

docker exec sak sak backup --db /data/sak.db --to /data/backup-1/sak.db
docker cp sak:/data/backup-1 ./backup-1

backup copies the database as it stands when the backup begins, through SQLite's online backup API, at --rate MiB a second (default 20), so sak's writes go on beside it, and copies the uploaded files that database records to <path>.images. It refuses a --to that is already there, ends only once PRAGMA quick_check, which reads at the same rate, passes on the copy, and lists any recorded file whose bytes were deleted before it reached them. sak serve --db <path> serves the backup as it lies.

Deploy

docs/deploy.md covers the manual deploy workflow that builds and publishes the sak docker image to the registry: how to trigger it, the runtime the job runs on, the secrets it reads, and how the NAS pulls and rolls back the published image. scripts/deploy.sh carries that logic and rehearses it locally with --local or --dry-run; deploy/runtime/manifest is the single authority on every external command the deploy path uses.

API

docs/api.md documents every route: projects, tasks, attributes, labels, comments, relations, search, saved views, paging and the revision protocol that guards concurrent writes.

Release tarballs

bash scripts/release.sh

builds the SPA and the server and writes three archives, each holding sak, web/dist/, this README and docs/:

  • dist/sak-<version>-macos-arm64.tar.gz
  • dist/sak-<version>-linux-x86_64.tar.gz
  • dist/sak-<version>-linux-arm64.tar.gz

It also writes dist/runner/, the runner directory sak serve --runner-dir serves the install scripts and the sak-runner builds for macOS and Linux from (see docs/deploy.md). The macOS archive is a native build, so the script itself runs on a macOS arm64 host; --linux-only drops it and packages the two Linux archives on any host. The Linux archives and the runner directory's builds are built with sx --target macos|linux|linux-arm --self-contained on any host, which needs zig on PATH (or in $SX_ZIG) for its libc. The script takes the compiler from $SX when that is set, and PATH otherwise.

The version is the VERSION constant in src/version.sx; it names the archives and answers GET /api/health. To cut a release, edit that constant and rerun the script. tests/deployed_upgrade.sx starts each build on tests/fixtures/deployed.sql, a database the deployed release leaves; once a release is deployed, bash scripts/deployed_fixture.sh <its sak> <version> <commit> moves the fixture onto it. The release script supplies the short git sha of the checkout it packages. Direct builds carry a valid SAK_BUILD_COMMIT when supplied and answer unknown otherwise.

Unpack and run:

mkdir -p sak && tar -xzf sak-0.3.13-macos-arm64.tar.gz -C sak && cd sak
./sak token create --db sak.db --name laptop
./sak serve --db sak.db