Skip to content
KoukeNekoPublic

About

Independent command-line client for Taiga 6 — stable JSON contracts, fixed exit codes, and conflict-safe writes for shells, CI and agents.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

Taiga CLI — an independent command-line client for Taiga

Taiga CLI

An independent command-line client for Taiga.
Readable terminal output for people, and a stable JSON contract for shells, CI, and agents.

Latest release Release downloads CI status Verified against Taiga 6.10.2 MIT licence

English · 繁體中文

Install · Getting started · Handbook · Changelog · Compatibility

taiga issue list
taiga issue create --subject "Fix token refresh" --type Bug
taiga issue close 42 --status Closed

# The same data, for a script, a CI job, or an agent
taiga issue view 42 --json --fields ref,subject,status,version
{"data":{"ref":42,"status":"Closed","subject":"Fix token refresh","version":8},"meta":{"contract":1}}

An independent command-line client for Taiga 6, not affiliated with the Taiga project. It drives projects, agile workflows, and wikis without leaving the terminal, discovers the API from the frontend's conf.json, including sites deployed under a /taiga/ subpath, and hands the token to your operating system keyring (on Linux, a file only you can read) instead of writing it into a config file.

One command serves both people and programs. Run it directly and you get aligned tables; add --json and you get a versioned contract, backed by fixed exit codes and JSON Schema descriptors, so a shell script, a CI job, or an LLM agent can drive it safely. The output format never changes just because it was piped — if you want JSON, you ask for it.

Writes behave predictably. Work items carry a version, and Taiga checks it per field: a change to a field someone else has already changed is refused instead of overwriting them, while their edit to a different field merges. Nothing is ever auto-merged on your behalf — a refusal stops the command so you can re-read and decide. Only idempotent GETs are retried. If a connection drops mid-write, the CLI reports ambiguous_commit and asks you to verify rather than blindly resending.

What it does

The whole Taiga workflow

Day-to-day operations for projects, epics, user stories, tasks, issues, sprints, and wiki pages are all here: list, view, create, edit, assign, comment, close, delete. Plus members and role permissions, webhooks, custom fields, eight families of workflow metadata, swimlanes, tags, due-date presets, and cross-project epic ↔ story links.

Work items accept a bare ref, a project#ref pair, or a pasted Taiga URL — all three work:

42
example-project#42
https://taiga.example.com/taiga/project/example-project/issue/42

A stable surface for automation

--json emits a meta.contract version, --fields selects columns, and taiga schema <command> returns that command's input and output JSON Schema along with safety and idempotency annotations — enough for an agent to decide whether a command may run unattended. Exit codes are partitioned by failure kind, and --dry-run resolves and displays the mutation it would send while guaranteeing that no write request leaves the process.

$ taiga schema issue create
{"data":{"command":"issue create","safety":"write","idempotency":"non_idempotent",
         "input_schema":{...,"required":["subject"]},"output_schema":{...}},"meta":{"contract":1}}

An agent reads safety and idempotency to decide whether a command may run unattended, and the schemas to build and validate the call — nothing is scraped from --help.

Hard to break things with

Deleting work items and metadata is verified by reading the target back. Attachment and CSV downloads stream to a 0600 temporary file, verify their digests, and land atomically without clobbering an existing file. Destructive actions require an explicit --yes when there is no terminal. Webhook secrets, application-token auth codes, and ownership-transfer tokens never appear in any output, including dry runs.

Safe to automate

A wrapper that misreports what happened to your data is worse than no wrapper, because a script acts on the answer. Three things follow from that:

  • A refused write is told apart from a bad one by structure, not wording. Taiga answers both with HTTP 400 under the same key and separates them only by the shape of the value, and it translates the sentence, so matching on the words would misfire and would fail outright on a server running in another language.
  • A write whose outcome is unknown says so. Interrupting a request already in flight does not un-send it, so the CLI reports ambiguous_commit and asks you to check rather than claiming a failure it cannot prove.
  • Concurrent writes are exercised, not assumed. An end-to-end test drives one project from twelve accounts at once and checks that no two accepted writes ever saw the same resulting version.

Concurrency and conflicts covers what Taiga refuses and what it merges.

Many sites, many projects

Profiles switch between Taiga sites, each remembering its own API URL and default project. You can also pin a profile and project to a single Git repository, stored in .git/config so it is never committed:

taiga project use example-project --local

Diagnosable when something breaks

taiga doctor checks frontend discovery, the API, authentication, and the default project one by one. When you need help, taiga doctor bundle produces a report you can share without worrying: version information, presence booleans, and status codes only — no URLs, usernames, project names, or credentials — created locally and never uploaded.

How it compares

One binary that a person, a shell script, a CI job, and an agent can all share — the lowest common interface, without running another service.

Web UI Basic CLI Taiga CLI MCP server
A person at a terminal ✅ ✅ ✅ —
Shell scripts — ✅ ✅ —
CI pipelines — ⚠️ ✅ ⚠️
AI agents — ⚠️ ✅ ✅
Stable JSON contract — ⚠️ ✅ ✅
JSON Schema per command — — ✅ varies
Dry-run — ⚠️ ✅ varies
Conflict-safe writes n/a varies ✅ varies
No extra daemon — ✅ ✅ —

Getting started

  1. Install. Homebrew, on macOS and Linux:

    brew install koukeneko/tap/taiga

    Scoop, on Windows:

    scoop bucket add koukeneko https://github.com/KoukeNeko/scoop-bucket
    scoop install koukeneko/taiga-cli

    On Debian/Ubuntu or Fedora/RHEL, add the signed APT/DNF repository for auto-updating installs, or grab a .deb / .rpm directly — see INSTALL.md.

    Or the install script, which verifies the download against the release checksums before it installs anything:

    curl -fsSL https://raw.githubusercontent.com/KoukeNeko/taiga-cli/main/scripts/install.sh | sh
    irm https://raw.githubusercontent.com/KoukeNeko/taiga-cli/main/scripts/install.ps1 | iex

    Release archives, manual checksum verification, and building from source are covered in INSTALL.md. On Windows, open a new terminal afterwards to pick up the PATH change.

  2. Log in. Run it with nothing else and answer two questions: the URL of any page inside your Taiga, with the hosted Taiga offered as the default, and how your account signs in. On macOS and Windows the token goes to the OS keyring:

    taiga auth login

    On Linux the token goes to ~/.config/taiga-cli/credentials.json, readable only by your user, and the login says so. A Linux keyring is often locked with no desktop to unlock it on, as over SSH, so it is used only when you ask for it with --credential-store=keyring. taiga auth status always says where the credential is kept.

    --credential-store (or TAIGA_CREDENTIAL_STORE) chooses this instead of leaving it to auto:

    Value Behaviour
    auto The file on Linux; elsewhere the OS keyring, or the file only where there is provably no keyring service (default)
    keyring The OS keyring only; fail rather than write a file
    file The file only; never contact a keyring. Suits servers
    none Keep nothing; pass the token in TAIGA_TOKEN

    The file is not encrypted. It is protected by file permissions alone, and backups or snapshots of the home directory include it.

    To skip the first question, pass --url with the URL of any page inside the Taiga web app, such as a project or backlog page; the API's address works too, and nothing beyond the site you typed is contacted. The hosted Taiga is https://tree.taiga.io/; the forum at community.taiga.io is a different site with its own accounts, and pasting its address offers the hosted app instead.

    taiga auth login --url https://taiga.example.com/taiga/ --profile company

    An account that signs in through GitHub or Google has no Taiga password. Choose that option at the second question, or pass --with-token, and taiga takes the tokens the web app holds: sign in on the web, open the browser's JavaScript console on that page, and run this to put them on the clipboard:

    copy(JSON.stringify({auth_token: JSON.parse(localStorage.token), refresh: JSON.parse(localStorage.refresh)}))

    Then paste the result at the prompt, or pipe it in:

    pbpaste | taiga auth login --url https://tree.taiga.io/ --with-token

    The refresh token in that object lets the login renew itself the way a password login does. A bare token works too, but then the login lasts only until that token expires, which is 24 hours on a default Taiga 6.

    A token imported this way comes without a refresh token, so it stops working when the server's access token expires, which is 24 hours on a default Taiga 6. TAIGA_TOKEN is the same thing for a script.

  3. Pick a project:

    taiga project list
    taiga project use example-project
  4. Start working:

    taiga issue list
    taiga issue create --subject "Fix token refresh" --type Bug
    taiga issue assign 42 --to alice
    taiga issue close 42 --status Closed
  5. Wire up automation:

    taiga issue view 42 --json --fields id,ref,subject,status,version --no-input

The full command reference, flag documentation, and per-subsystem behaviour live in the handbook wiki, which includes worked automation recipes for CI, shell scripts and agents.

Compatibility

  • Taiga 6.10.2, verified by Docker E2E against a pinned image digest
  • macOS, Linux, and Windows on amd64 and arm64, built as pure Go (CGO_ENABLED=0)
  • Password login, existing bearer tokens, and refresh-token rotation

The detailed matrix and known limits are in COMPATIBILITY.md.


Technical reference

Setting resolution

General settings live in the operating system's user config directory. Tokens are never written there:

current_profile = "company"

[profiles.company]
api_url = "https://taiga.example.com/taiga/api/v1/"
project = "example-project"

Resolution order, highest first:

command flag
→ TAIGA_PROFILE / TAIGA_API_URL / TAIGA_PROJECT / TAIGA_TOKEN / TAIGA_CREDENTIAL_STORE
→ Git-local taiga.profile / taiga.project
→ current profile
→ safe defaults

JSON contract

Successful data goes to stdout and errors go to stderr, never mixed into one stream. Single records use data, lists use items and page, and both carry meta.contract:

{
  "data": { "id": 123, "ref": 42, "subject": "Fix token refresh", "version": 7 },
  "meta": { "contract": 1 }
}

Within one contract version only optional fields are added. Removing a field, renaming it, or changing the type of an existing one requires a version bump and migration notes in the release.

Exit code Meaning
0 success
1 unexpected internal failure
2 usage / schema
3 authentication
4 forbidden
5 not found
6 OCC conflict
7 validation / ambiguity
8 throttled
9 transport / upstream
10 confirmation required
11 ambiguous commit
130 interrupted before finishing

1 means the CLI hit something it has no classification for, and is worth reporting as a bug. 130 follows the shell convention of 128 plus the signal number rather than taking a place in the table above, because stopping a command is a decision rather than a way it failed; an interrupt that stopped a write in flight reports 11 instead.

Security principles

  • Passwords are never accepted on the command line
  • Authorization headers, passwords, and tokens never appear in verbose logs
  • Only GETs are retried automatically, with a bounded count; POST and PATCH are never resent blindly
  • A write whose outcome is unknown reports ambiguous_commit instead of retrying
  • OCC conflicts are never auto-merged or overwritten
  • Attachment downloads never send the API bearer token to the media URL
  • TLS verification is always on

Development and testing

The fast loop, no Docker required:

make test
make test-race
make lint

Integration tests against a real Taiga server:

make test-integration

The harness uses a dedicated taiga-cli-e2e Compose project on localhost:19000, creates its own throwaway account, project, and issues, and tears down only its own containers and volumes — it never touches a Taiga instance you use day to day.

Rebuilding cross-platform release artifacts:

make release \
  VERSION=v0.1.0 \
  COMMIT="$(git rev-parse HEAD)" \
  SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)"

The same source, Go toolchain, version, commit, and epoch produce byte-identical Linux and Windows archives. The maintainer release process is in RELEASING.md. macOS archives are the exception: notarization requires a secure timestamp from Apple, so a Developer ID signature can never reproduce. Their contents are otherwise built identically.

Cobra Reproducible builds SPDX 2.3 SBOM Codacy code quality

Support

If Taiga CLI is useful to you, you can support development:

Buy Me a Coffee

Trademarks

Taiga is a trademark of its respective owner. This project is an independent client that is not affiliated with, endorsed by, or sponsored by the Taiga project or its maintainers, and uses the name only to describe the software it works with. The Taiga team confirmed on the community forum that a small, independent third-party client describing itself with the Taiga name is not a problem.

License

MIT © KoukeNeko

About

Independent command-line client for Taiga 6 — stable JSON contracts, fixed exit codes, and conflict-safe writes for shells, CI and agents.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages