@coldtea/tasks
v0.1.2
Published
The coldtea-tasks command line: read and file Coldtea tasks from a terminal or CI.
Downloads
529
Readme
@coldtea/tasks — the coldtea-tasks CLI
Read and file Coldtea tasks from a terminal or CI: list what is open, look a
task up by the identifier a person would quote, and file one from a script.
Every command talks to the public /v1 API and nothing else.
Install
The published package has ZERO runtime dependencies: @coldtea/cli-core,
the half every Coldtea command line shares, is bundled into dist/main.js
at build time. In the monorepo, npm install builds it via the package's
prepare script; npm run build -w @coldtea/tasks rebuilds it by hand.
Needs Node >= 20.6.
The package installs two names for the same binary: coldtea-tasks, and
ctask for short — fewer tokens for the agents that call it most. The
0.1.0 two-word spelling (ctask tasks list) keeps working; the noun-less
form is the one the help teaches.
Inside this monorepo:
node packages/tasks-cli/bin/coldtea-tasks.mjs --helpThe API reference
The CLI talks to /v1 and nothing else, so the API reference is also the
CLI's reference for what any command can do:
https://www.coldtea.ai/openapi/coldtea-v1.jsonOpenAPI 3.1, generated from the routes themselves. It covers QA and Tasks together — one API, one key, scopes saying which product an endpoint belongs to.
Auth
Create an API key in the Coldtea dashboard with a Tasks scope, then:
export COLDTEA_API_KEY=coldtea_sk_…
coldtea-tasks whoamiThe key identifies your TeaHouse; there is no TeaHouse flag, and /v1
rejects one with a 400 rather than ignoring it. --api-key overrides the
environment. COLDTEA_BASE_URL points the CLI at another API origin —
https only (every request carries the key), with loopback http allowed for
dev and COLDTEA_ALLOW_INSECURE_HTTP=1 as the explicit opt-out for an
http-only private host.
Two scopes exist. tasks:read lists and reads; tasks:write also creates.
tasks:* is a real scope rather than shorthand, and a key holding it gains
any tasks scope added later — where a key holding the enumerated two does
not. A key with only QA scopes authenticates fine and is refused by every
command here, which whoami says out loud rather than leaving you to infer
from a 403.
Getting started
A title is enough to file a task: the server defaults the team to your TeaHouse's only team, and the column to that team's Todo. The reads below are for when you want to CHOOSE — several teams, a different column, an assignee:
ctask whoami # which TeaHouse, and what the key may do
ctask create "Title" # files it in your only team's Todo column
ctask teams list # team ids, for when you have several
ctask states list # board columns (--team only if several teams), for --state
ctask labels list # label ids for --label
ctask members list # user ids for --assignee
export COLDTEA_TEAM_ID=team_… # stop repeating --teamEvery one of those is a read. Teams, columns, labels and members are created
in the dashboard, because each of them changes what everyone on the team
sees; tasks:write means tasks.
members list carries no email addresses. Assigning needs the id and
recognising a colleague needs the name, and a CLI's output ends up in logs
that are kept and sometimes public.
Tasks
ctask list
ctask list --state wfs_… --priority urgent --limit 50
ctask list --label lbl_… --label lbl_… # BOTH labels (repeats combine as AND)
ctask list --search "flaky login" # title match, case-insensitive
ctask list --updated-since 2h # 15m, 2h, 7d, or an epoch-ms instant
ctask list --state-type started # a column's MEANING, across every team
ctask list --parent ORA-12 # one task's subtasks
ctask get ORA-12
ctask get ORA-12 --description
ctask create "Checkout drops the saved card"
ctask create "…" --team team_… --state wfs_… # choose instead
ctask update ORA-12 --priority urgent --assignee usr_…
ctask update ORA-12 --assignee none # clear it
ctask update ORA-12 --state wfs_… # move between columns
ctask delete ORA-12A task is named either by the identifier a person would quote — ORA-12, the
team's key and the task's number — or by its canonical id. Both work
everywhere a task id is taken.
Filters combine, and filtered pages can run short. Every filter
narrows; a page may hold fewer than --limit rows and still have a next
page, so follow meta.nextCursor, never row counts. --updated-since
refuses bare 10-digit numbers on purpose: date +%s prints seconds, the
API takes milliseconds, and a guess between them would silently match
every task ever.
Priority is a word, not a number. none, urgent, high, medium,
low. The underlying scale runs 1 for urgent to 4 for low, which is the
reverse of what most people guess, so the number is never the input.
--state is optional, and the default is the server's. Omitted, the
task lands in the team's Todo column — its leftmost unstarted state, the
same column the product itself files into. To choose, states list shows
the ids. Read a column's type to decide what it MEANS — a team may hold
several columns of one type, so completed is the test for done, not a
name. A blank --state or --team (usually an unset shell variable) is
refused rather than read as "the default".
Descriptions are Markdown, and tasks list does not print them:
ctask create "Flaky checkout" --description-file ./report.md
cat report.md | ctask create "…" --description-file -
ctask get ORA-12 --descriptionA description is where pasted output goes — an agent filing a task through
MCP can put a log, a stack trace or a terminal buffer in it — and tasks
list runs in CI, where output is retained, shipped to log aggregators, and
public on an open-source repository. So the body is something you ask for,
once, about one task. --json still carries descriptionPreview, because
--json is the API's own payload and filtering it would make this CLI's
output a different contract from the endpoint it reports.
tasks update --description-file REPLACES THE WHOLE BODY, and the previous
one is not recoverable. There is no confirmation and no undo. It is the
sharpest edge on this surface and it is written down rather than guarded,
because the guard does not belong here: /v1 has no read-modify-write for a
description, and a CLI that invented one would be promising a guarantee it
cannot keep. If it ever earns a real guard, that guard is an
expectedUpdatedAt on the endpoint — the same one tasks update already
takes for the task itself.
Read the body first if you are not certain what you are replacing:
ctask get ORA-12 --description > before.mdTwo writers, one task. tasks update takes --expect-updated-at, the
updatedAt you last read. If somebody wrote to the task in between, the
change is refused with exit 3 rather than applied over theirs. Without it the
last write wins, which is right for a caller with no prior read to be stale.
UPDATED=$(ctask get ORA-12 --json | jq .updatedAt)
ctask update ORA-12 --title "…" --expect-updated-at "$UPDATED"What is not here yet. Attachments, a task's activity history, and the implementation plans and session logs agents write. The last of those is deferred deliberately rather than pending: their visibility is per-user, so an API key could only ever see the team-shared ones, and a view that quietly returns fewer rows than the desktop shows is worse than no view.
Output, exit codes, CI
--jsonprints one machine-readable JSON document on stdout, in the API's own envelope:{ data, meta },meta.requestIdincluded. List commands carrymeta.nextCursorso scripts page exactly like API callers — copy it until it is null, never count rows; a page can hold fewer than--limitresults and still have a next page.- Errors always go to stderr, with the request id — quote it in support — so
--jsonstdout stays parseable even on failure. - Color only on a TTY, and never when
NO_COLORis set. Nothing prompts without a TTY: piping a description with--description-file -on a terminal is refused rather than left to hang. - Exit codes are a contract:
0done ·2couldn't do it, try again — a 5xx, a timeout, rate limiting ·3a conflict a person must resolve ·4you have to change something — auth, configuration, or the request itself.
There is no exit 1. That code means "the thing under test is broken", which is a verdict — QA reports it about an app, and Tasks has nothing to say about it, because a task cannot be broken. Nothing in this package can produce it: commands are typed against an exit union that excludes 1, so a command trying to return one does not compile.
2 is retryable and 4 is not. A 5xx, a timeout or rate limiting is 2, and running the step again may work. A missing, malformed or under-scoped key is 4, and only new configuration fixes it — a CI step that retried on 4 would be retrying a key that will never start working.
A malformed request, an unknown id, a stale cursor and an oversized body are
4 as well. They are not your credential's fault, but the exit code is not a
category label — it is an instruction, and 2 says run me again.
tasks get ORA-99999 returns the same answer forever, so anything that
cannot change without you changing it belongs on 4. 402 stays on 2, because
credit arrives without you doing anything.
