▍ humdrum codex / dots v1.0.0
7.1 KB raw

dots-cli — v1 design

Date: 2026-07-29 Status: Approved pending user review Repos touched: this repo (new, Go CLI) + humdrum-tiv/dots (small server PR)

Purpose

A local command-line client for Dots (dots.humdrum.one) optimized for reflex-speed logging from the terminal: paint a block, add a day note, glance at today — faster than reaching for the phone. Third client after web and iOS; rides the same API surface.

Explicitly not an offline client: no local store, no sync engine, no outbox. Every command is one or two HTTP calls against the production API. If the network is down the command fails fast and says so.

Command surface (v1)

dots auth                          Link CLI to account (one-time token paste)
dots log [time|range] [activity]   Paint logged block(s)
dots clear <time|range>            Clear logged block(s)
dots note "text" [--date D]        Append a day note
dots today [--date D] [--json]     Day grid readout
dots types                         List active activity types

Global flags: --date YYYY-MM-DD where noted (defaults to today, device timezone), --yes/-y to skip confirmation prompts, DOTS_URL env var to override the server (dev/testing; defaults to https://dots.humdrum.one).

Time syntax

Dots blocks are blockSize-minute slots, where blockSize is a per-day setting of 15, 30, or 60 (blockIndex = minutesSinceMidnight / blockSize). The CLI reads day.blockSize from the day payload and does all slot math against it. Accepted forms (examples at blockSize 30):

Argument parsing: the first positional arg is treated as a time/range only when it matches the time syntax (H:MM, HH:MM, or a --joined pair); otherwise it is the activity. So dots log archer = current slot + archer, dots log 14:30 = that slot + plan-fill.

Logging semantics

Activity matching

Client-side fuzzy match against the active types returned by the day payload: case-insensitive prefix match first, then substring. Multiple hits → numbered pick prompt (error under non-TTY). Zero hits → error listing closest names. The CLI cannot create activity types (same v1 rule as the native app); dots types shows what exists.

dots today output

Auth

Server changes (humdrum-tiv/dots, one small PR)

Modeled on the existing widget-token machinery (lib/db/widgetTokens.ts, /api/widget/token, /api/widget/data):

  1. cli_tokens table — clone of widget_tokens (user id, token, created at). Separate row so revoking the widget token doesn't kill the CLI and vice versa.
  2. /cli page — signed-in page with generate/copy/revoke, mirroring the widget token UI.
  3. Shared auth helper resolveUserId(request) — tries Clerk auth() first, falls back to BearergetUserIdByCliToken. Applied to exactly:
    • GET /api/widget/data (additionally accept CLI tokens)
    • POST/PUT /api/days/[date]/blocks
    • POST /api/days/[date]/notes
  4. Nothing else changes. Block-write business logic is reused exactly as web/iOS use it.

GET /api/widget/data?date=D&tz=TZ already returns blocks, plan blocks, activity types, and events — everything the CLI reads. One GET feeds today, type matching, plan-fill lookup, and the overwrite diff; write commands are 1 GET + 1 POST.

CLI internals

cmd/dots/main.go        cobra root + command wiring
internal/api/           HTTP client: GetDay, WriteBlocks, ClearBlocks, AddNote, VerifyToken
internal/auth/          keychain load/store, auth command flow
internal/day/           slot math (time→slot index, tz), range parsing, fuzzy match,
                        plan-fill resolution, overwrite diff
internal/render/        today grid (lipgloss truecolor), --json passthrough

Errors

Plain one-line messages to stderr, exit code 1. Network unreachable → "can't reach dots.humdrum.one". No retry/backoff in v1 — it's a reflex tool; the user reruns.

Testing

Release / repo

Out of scope (v1)

Offline queueing, plan-block editing, type creation, timeline/stats/week views, dots tui, SSH serving, non-macOS packaging, retry logic.