cyberine
UsageRun recipes

JSON reads of the board — status --json and next --json (ka7fx)

Two read-only views of the board print one JSON object on one stdout line, for a program that narrates the board (the cyberine-taskboard plugin, a script, a status bar) instead of parsing the text views. Each carries a schema string; a field is only ever added under the same schema, and a rename or removal bumps its number. The full CLI and the taskboard bundle build both from the same module (src/tasks/boardJson.ts), so for the same board and the same directory they print byte-identical JSON. Warnings (lint findings, a CRLF manifest) still go to stderr; the scope and stale notices of the text views do not, because the JSON carries scope, hidden and stale itself. cyberine tasks board --json (the HTML page's data, with briefs, archive and journal) is unchanged and is a third, heavier read.

cyberine tasks status --json            # board-status/1
cyberine tasks status --json --all      # inside a submodule: every task, not only the submodule's
cyberine next --json                    # board-next/1 (`cyberine tasks next --json` is the same command)
node <plugin-dir>/cli/tasks.mjs status --json   # the taskboard bundle, same bytes
node <plugin-dir>/cli/tasks.mjs next --json

status --json (schema board-status/1) is the board view with counts and each task's effective status:

{"schema":"board-status/1","manifest":"/repo/.local/tasks/ACTIVE.md","scope":"","hidden":0,
 "counts":{"doing":0,"todo":1,"waiting":1,"blocked":1,"backlog":1,"done":0,"forbidden":0,"unknown":0,
           "ready":1,"stale":0,"recurring":0,"recurringDue":0,"pending":0},
 "tasks":[{"id":"k5d45","title":"First task","status":"todo","baseStatus":"todo","eff":"todo",
           "deps":[],"unmet":[],"blocker":"","reason":"","duplicateOf":"","path":"","owner":"",
           "created":"2026-09-29","ready":false,"stale":false}],
 "recurring":[{"id":"R1","title":"...","state":"DUE","days":3,"every":"1w","lastDone":"2026-09-19","exec":""}]}
  • manifest is the absolute manifest path; scope is the submodule path the view is limited to ("" for the whole board) and hidden how many tasks that scope hides.
  • tasks is in manifest order. status is the effective status (manifest header plus the pending overlay), baseStatus the manifest header alone, and eff the display label the board page uses: waiting is a [todo] whose deps are not all done, blocked a [todo] with a blocker:, unknown an unrecognised bracket, otherwise the status. unmet lists the deps that are not done (archived ids count as done). ready marks a [backlog] task whose deps are met, stale a [doing] task past NF_TASKS_STALE_DOING_DAYS (default 3). String fields the manifest does not set are "".
  • counts partitions tasks by eff (the eight status keys sum to tasks.length), then ready, stale, recurring, recurringDue (NEW or DUE entries) and pending (overlay lines not yet reconciled). The text view's todo count includes the waiting tasks; this one does not.

next --json (schema board-next/1) is the dispatch view, the same sets cyberine next prints:

{"schema":"board-next/1","manifest":"/repo/.local/tasks/ACTIVE.md","scope":"","hidden":0,
 "counts":{"done":0,"runnable":1,"waiting":1,"blocked":1,"doing":0,"backlog":1,"forbidden":0,"duplicate":0},
 "runnable":[{"id":"k5d45","title":"First task","priority":"","deps":[],"path":""}],
 "waiting":[{"id":"kk12s","title":"Second task","unmet":["k5d45"],"cycles":[]}],
 "blocked":[{"id":"kd6rf","title":"Fourth","blocker":"waiting on vendor"}],
 "doing":[]}
  • runnable is in dispatch order (p0 first, then manifest order); priority is "" when the task sets none.
  • waiting holds [todo] tasks with unmet deps and blocked those held by a blocker:, the same words status --json uses. The text view of cyberine next labels these two the other way round ("Blocked (deps not done)", "Waiting on external blocker"); the JSON follows the board's eff vocabulary so one reader needs one meaning. cycles lists the unmet deps whose duplicate-of: chain loops.
  • counts.done is every id that satisfies a dependency, manifest [done] plus the archive, over the whole board even inside a submodule scope, exactly like the text header; the other counts are scoped. Duplicates (duplicate-of:) appear only in counts.duplicate.
  • An empty or missing manifest prints nothing on stdout and exits 1 with the same stderr message as the text view. --json with --dispatch exits 2: the JSON view is a read.

Known Limitations and Deferred Work: there is no JSON for status --summary, or the banner line; the banner already has cyberine tasks banner --json. Only the two schemas above are covered by the byte-identical promise; the text views still differ between the CLI and a branded bundle, whose messages name its own program. No selftest pins the shapes yet; they were compared by hand (cmp) between the full CLI and a branded bundle on a scratch board.