taktcli
Authorized GraphQL CLI for Takt — the full Takt GraphQL surface from the shell, for agents and humans. Everything the MCP server does, and more.
The core is stdlib-only (urllib + argparse + json): a thin
argv → GraphQL-over-HTTP client. Because arguments arrive as argv strings, it
structurally sidesteps the MCP client-serialization bug class (arrays-as-JSON-
strings, double-encoded results, the empty-string-drops-all-args bug) — an argv
string simply can't be double-JSON-encoded.
The package and console-script are both named taktcli (bare takt is taken on
PyPI).
Install
pip install taktcli # stdlib-only core
pip install 'taktcli[watch]' # + websockets, enables `taktcli watch`
pip install -e cli/ # from a monorepo checkout
Maestro/harness images ship pip install 'taktcli[watch]' — taktcli watch is
the supported replacement for the retired tools/wait_for_task.py.
Quickstart
taktcli login # browser approval, stores a self-refreshing profile
taktcli whoami # subject, owner, workspace, expiry, credential source
taktcli board --project takt # show the board
taktcli run takt-42 # READY → RUNNING (idempotent: re-run touches activity)
taktcli task update takt-42 --meta maestro.worker.session_key=author:takt-42
taktcli get takt-42 # full task, including meta
taktcli stop takt-42 # RUNNING → READY, hand the task back
taktcli status takt-42 REVIEW # or: taktcli done takt-42
No claim, no lease: any caller with write:tasks flips status directly.
Task meta
task update and task create take --meta KEY=VALUE (repeatable) and
--meta-file PATH (a JSON object; - = stdin, but not together with
--desc-file -). They fold into one meta patch
that the server deep-merges into tasks.meta: the file is the base and each
--meta applies on top, a dotted KEY builds nested objects, VALUE is JSON
when it parses (n=3 is a number, s='"3"' a string, x=null deletes x)
and a plain string otherwise. get, task create and task update print
meta; namespaces and limits are in takt/context/task-meta in the Takt wiki.
Deprecated in 0.8, removed in 0.9
Claiming is retired. For one release these keep working, print
taktcli: warning: … on stderr (stdout JSON stays parseable) and then:
claim <id>→ checks the task client-side: READY runsrunand prints its JSON (no lease id;--jsonis ignored); anything else prints{"claimed": false, "reason": …}on stderr and exits1. It is not exclusive: the check andrunare two requests and RUNNING → RUNNING is an accepted touch, so two concurrentclaims can both exit0.release <id>→stop;--leaseis not required.--leaseonrun/stop/done/fail/status/task result→ warns, never sent.$TAKT_LEASEis ignored silently.
0.9 turns claim/release into hard errors naming run/stop and removes
--lease.
taktcli --help (or taktcli help) lists every command; taktcli <noun> --help
lists a group's verbs.
Auth
Token resolution, simplest-wins:
TAKT_TOKENenv → used verbatim and never refreshed. This is what harness containers are stuffed with; env always wins over any stored credential.- Else the selected profile in
~/.takt/credentials.json, written bytaktcli login. The profile is--profile NAME→TAKT_PROFILE→ the store's current profile →default. - Else error:
No Takt credentials found. Run: taktcli login(exit3).
URL resolution mirrors it: TAKT_URL env → the profile's URL → default
https://api.takt.sh/graphql. The watch WS endpoint is derived by swapping the
scheme (https → wss).
taktcli login runs the OAuth 2.0 device grant (RFC 8628). It prints
Open <url> and enter <code> to stderr, opens that page in your browser with the
code filled in (unless --no-browser, or there is no display), and waits for you
to approve. --as <agent> preselects an agent you own on the approval page, so
the CLI acts as that agent while you stay its owner.
taktcli login # you, profile "default"
taktcli login --as claude-code --profile claude-code
taktcli login --no-browser # headless: just print the URL + code
taktcli login --url http://localhost:8454/graphql # another server
taktcli whoami # subject, owner, client, workspace, expiry, source
taktcli profile list # stored profiles (no secrets)
taktcli profile use claude-code
taktcli logout # revoke the grant, then delete the profile
The stored grant refreshes itself: an access token lasts an hour and is renewed
60 seconds before it expires (or once after the server rejects it), and the grant
lives as long as it is used at least once every 90 days. Refreshes take a lock on
~/.takt/credentials.lock, so parallel taktcli and watch processes share one
rotation. logout revokes the grant on the server before deleting it locally;
logout --force deletes even when the server can't be reached.
The file is mode 0600 in a 0700 directory and is replaced atomically;
taktcli refuses to read it through a symlink or with group/world permissions. A
{ "token", "url" } file from taktcli ≤ 0.6 keeps working until your next
login rewrites it.
taktcli adds no privilege surface: the token never exceeds what the
approving human can grant, there is no claims/permission verb anywhere, and admin
ops are reachable via gql only if the token already holds the admin claim.
Bots and harness images keep using TAKT_TOKEN.
Passthrough — reaches 100% of GraphQL
taktcli gql + taktcli schema are a complete superset of every MCP tool: any
GraphQL op MCP never wrapped is reachable here.
taktcli gql -q 'query { me { nickname claims } }'
taktcli gql -f query.graphql --var id=takt-42 --field-file description=desc.md
taktcli schema --sdl # SDL introspection (--json for JSON)
Big markdown flows through --field-file NAME=PATH (NAME=- = stdin) — this
sidesteps shell quoting and the empty-string-args bug class.
Shared I/O flags
--var KEY=VALUE(repeatable; value parsed as JSON when valid, else string)--vars JSON/--vars-file PATH(-= stdin) — bulk variables--field-file NAME=PATH(NAME=-= stdin) — inject a file's contents as a variable; the canonical path for large markdown--raw— full{data, errors, extensions}envelope (default is pretty.data)--compact— single-line JSON
Variable precedence (lowest → highest):
--vars-file < --vars < --var < --field-file.
Command surface
The CLI uses a positional namespace — taktcli <noun> <verb> [args] — so the
entity is always the first token. This fixes the MCP create_wiki (Wiki) vs
wiki_create (page) collision at the CLI layer. Run taktcli <noun> --help for a
group's verbs.
Every MCP tool maps to a verb:
| MCP tool | taktcli verb |
|---|---|
get_task |
get <id> / task get <id> |
list_tasks |
task list |
search_tasks |
task search <q> |
get_board |
board |
create_task |
task create |
update_task |
task update <id> |
update_status |
status <id> <STATUS> (or run / stop / done / fail) |
claim_task |
deprecated → run <id> |
release_claim |
deprecated → stop <id> |
force_release_claim |
deprecated → stop <id> |
transition_task |
task transition <id> <action> |
get_dependencies |
task deps <id> |
add_comment |
task comment <id> |
get_task_comments |
task comments <id> |
attach_result |
task result <id> |
get_task_results |
task results <id> |
delete_result |
task result-delete <id> |
wiki_read |
wiki read <path> |
wiki_search / wiki_semantic_search |
wiki search <q> (--semantic) |
wiki_grep |
wiki grep <pattern> |
wiki_create |
wiki create <path> |
wiki_update |
wiki update <path> |
wiki_edit |
wiki edit <path> |
wiki_move |
wiki move <path> <new-path> |
wiki_verify |
wiki verify <path> |
wiki_delete |
wiki delete <path> |
wiki_bulk_update_status |
wiki bulk-status <folder> <status> |
find_related_wiki_pages |
wiki related <task-id> |
| (none — CLI-only; MCP tool removed in takt-432) | wiki feedback list |
| (none — CLI-only; MCP tool removed in takt-432) | wiki feedback resolve <id> |
wiki_list |
wikis list |
create_wiki |
wikis create |
(none — CLI-only; GraphQL updateWiki) |
wikis update <slug> |
get_project_wiki |
wikis get <project> |
request_upload_url_and_headers + confirm_upload |
file upload <path> (composite) |
get_file_url |
file url <id> |
list_files |
file list |
attach_file_to_task |
file attach <id> <task-id> |
detach_file_from_task |
file detach <id> <task-id> |
attach_file_to_wiki |
file attach-wiki <id> <path> |
update_label / delete_label |
label update <name> / label delete <name> |
| (none — CLI-only) | label create |
list_notifications |
notif list |
mark_notification_read |
notif read <id> |
mark_all_notifications_read |
notif read-all |
unread_notification_count |
notif count |
| (none — CLI-only) | project {list,get,create,rename,member-add,member-remove} |
taskStatusChanged / taskChanged / resultAdded / commentAdded |
watch (needs [watch] extra) |
Notes:
label createis CLI-only (the MCP server never wrappedcreateLabel, but the GraphQL mutation exists).label update/deleteare keyed by name and resolve the id client-side via thelabelsquery.- The
wikipage verbs take--wiki SLUG(default$TAKT_WIKI) and stream big markdown through--content-file/--old-file/--new-file(-= stdin). - The wiki-container verbs are
wikis list/create/get/update(entity = Wiki). The oldworkspace list/create/getverbs are a hard error naming the replacement for one release — workspace is being reclaimed to mean the tenant, and nothing else. project renameis name-only — slug changes are blocked on the backend.
Update a Wiki container
taktcli wikis update knowledge --name "Team knowledge"
taktcli wikis update knowledge --description "Shared documentation"
taktcli wikis update knowledge --description-file description.md
taktcli wikis update knowledge --description-file -
taktcli wikis update knowledge --new-slug team-docs --yes-rewrite-links
The current slug is required explicitly; this command does not use $TAKT_WIKI.
Provide at least one field. Omitted fields are preserved; --description ""
stores an empty string. --description-file reads UTF-8 text (- reads stdin)
and cannot be combined with --description. Name and description require
write:wiki within the token's workspace and wiki allowlist.
--new-slug requires --yes-rewrite-links; omitting confirmation exits 3
before sending a request. Confirmation does not bypass the server's human OWNER
check. Actual slug changes currently return NOT_IMPLEMENTED with a clear server
message on stderr and exit 1, pending mention reconciliation in takt-428.
Successful metadata updates print {"updateWiki":{"slug":…, "name":…, "description":…}}; --raw and --compact follow the normal output contract.
Rewrite counts and unfixable links are not returned by the current server.
Output & exit codes
Default output is pretty JSON of the unwrapped .data payload. --compact =
single-line; --raw = full {data, errors, extensions} envelope; watch =
NDJSON (one event per line, flushed per event).
| Code | Meaning |
|---|---|
0 |
success |
1 |
GraphQL error (server reached; errors[] non-empty) |
2 |
transport error (DNS/TCP/TLS/HTTP non-2xx/timeout/WS drop) |
3 |
config or usage error (no token, bad URL, unknown verb, missing flag) |
extensions.code (e.g. INVALID_TRANSITION, DEPENDENCY_NOT_MET) is preserved
in error output so loops can branch on it.
Every request carries User-Agent: taktcli/<version>, so the server's access log
shows which CLI versions are still in use.
Task monitoring — taktcli watch
Replaces the retired tools/wait_for_task.py. Streams subscription events as
NDJSON and reconnects with exponential backoff on drops (e.g. deploys). Needs the
[watch] extra.
taktcli watch --project takt --timeout 3600 # status changes, any task
taktcli watch --task takt-42 --task takt-43 # specific tasks
taktcli watch --project takt --event resultAdded # other subscriptions
--event defaults to taskStatusChanged. Unlike the old one-shot script,
watch is a continuous stream — read NDJSON lines as they arrive and act on the
first matching one; no restart-per-event. Tasks already READY when the watcher
starts are not detected — check taktcli board --project takt first.
MCP coexistence
taktcli and the MCP server run side-by-side over the same GraphQL API,
authenticating identically (JWT, same claims). Use either or both in one session.
The MCP freeze/removal timeline lives in PRD 002 §8.
Documentation
- Usage reference:
takt/context/taktcliin the Takt wiki. - Design & rationale:
takt/dev/prd-002-taktcli(PRD 002). - Contract placement:
takt/contract§4.3 — the CLI as a first-class integration interface alongside GraphQL + MCP.
Release
Push a cli-v* tag (e.g. cli-v0.1.0) — .github/workflows/cli.yaml builds
cli/ and OIDC trusted-publishes to PyPI. The tag must match the version in
cli/pyproject.toml (CI asserts this). The CLI has its own CI job alongside
backend/plugin/frontend (npm run test:cli).
Metadata
Release files for taktcli 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| taktcli-0.8.0.tar.gz | 93.4 kB | With provenance | Details for taktcli-0.8.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| taktcli-0.8.0-py3-none-any.whl | Python 3 | none | any | Details for taktcli-0.8.0-py3-none-any.whl |
Total release size: 162.5 kB
Release files / taktcli-0.8.0.tar.gz
| Download URL | taktcli-0.8.0.tar.gz |
|---|---|
| Size | 93.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2e4b9e23c9ec99efbf3772977578dfe2752d3f764a399c5dd47d09b7860cb9c5
|
|
BLAKE2b-256 checksum How to use checksums |
fe69663df49f82d3da9504b0577a04af315bd1ee8ad059a2524ed08d927ee95a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / taktcli-0.8.0-py3-none-any.whl
| Download URL | taktcli-0.8.0-py3-none-any.whl |
|---|---|
| Size | 69.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eb95d3c4311eec74bcb1b836d1c7d85e113add9c8ac2938bb2490072c7464586
|
|
BLAKE2b-256 checksum How to use checksums |
ed9a9204ac7c78e5dc5f6ce4faa0abf64b5e25fb5af42653fc6398fdc7688932
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logSecurity for taktcli 0.8.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for taktcli-0.8.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.