maia
The M.AI.A board from a terminal, for people and for the coding agents they run there.
It talks to the same public HTTP API the web app uses, so it inherits the server's rules rather than restating them: any active project member can move, edit, assign or reassign any ticket, and observers are read-only.
Install
uv tool install maiactl # from PyPI (the package is maiactl; the command is maia)
maia login # opens your browser, then asks which workspace/project
maia # where am I, what next
maia status --full # everything: version vs PyPI, accounts, workspaces, projects
maia doctor # if anything feels off
maia upgrade # newest release, via whatever installed you (uv / pipx / pip)
uv tool upgrade maiactl # the standard path works too
Bare maia is a status card (offline): who you are, which workspace and
project, and what to try. maia doctor checks version, server, token,
workspace and project, and says how to fix each.
The server check probes the API itself (an unauthenticated /api/v1/auth/me
must answer JSON), not /health, which the web host answers on the
backend's behalf.
Works on macOS (Apple Silicon and Intel), Linux and Windows — it's a
pure-Python package. On Windows the config lives in %APPDATA%\maia\config.json,
colour is enabled in the legacy console automatically, and output never
fails on a character your code page can't show.
login runs the same brokered PKCE flow the desktop app uses: it opens
Google in your real browser (always showing the account chooser, so a
profile's current session is never reused silently) and polls until the
backend releases a token,
then finishes setting you up: if you're in several workspaces it asks which,
and if that workspace has several projects it asks which of those. One of
each is picked for you without a question. In a script or an agent shell
(no TTY) there's no prompt; pass --tenant and then maia use <code>.
The token is written to ~/.config/maia/config.json with mode 0600.
Several accounts at once
Like gh, you can be signed into more than one account and switch between
them. An account is (email, server), so the same person on prod and on a
local backend are two accounts — which is the point.
maia login # adds an account; never evicts an existing one
maia auth list # all of them, active one marked with *
maia auth switch priya # by email, or any unique fragment
maia auth logout --account priya # sign out of just that one
maia auth logout --all
Each account remembers its own workspace and project, so switching restores
where you were. For a single command, -a (--as) acts as another account
without switching, and nothing that command does is saved:
maia -a priya@cydratech.com ls --mine
maia ls --mine -a priya # flags may come before or after the command
-a can't be combined with login, logout or auth switch; those manage
saved accounts, so name the account directly (logout --account priya).
Workspaces
maia workspaces # the ones this account belongs to
maia workspace "Cydra Tech" # switch
Switching workspace clears the selected project, because a project belongs
to exactly one workspace — keeping it would point at something the account
can no longer see. Pick a new one with maia projects and maia use.
It is a normal app session token, not a scoped API key, so it carries your
full access and expires on the server's schedule. Treat the file as a
credential. maia logout removes it.
For CI or a throwaway shell, skip the file entirely:
export MAIA_TOKEN=... MAIA_TENANT_ID=...
export MAIA_PROJECT=MVP MAIA_API_URL=https://maia.cydratech.com
Anything set this way applies to the current run only and is never written
to disk: a CI job can't persist its credentials, and MAIA_PROJECT=X maia use Y won't change your saved project. Commands that would normally save
say (not saved: …) on stderr. MAIA_ACCOUNT=priya selects a saved account
for one run; a name that matches nothing is an error, not a silent fallback.
With MAIA_TOKEN set, logout and auth switch refuse, since there is no
saved session in play.
Reading
maia ls # active tickets, doing first (alias: maia tickets)
maia status # same as bare `maia`
maia ls --mine --status doing
maia ls --stage "In QA"
maia ls -s payment # title/description search
maia show MVP-42 # detail + comments
maia show MVP-42 --history
maia history MVP-42 # the ticket's story (alias: maia log)
maia board # stages and card counts
maia members # who's on it, and their roles
maia stories
maia files MVP-42 --download ./tmp
maia media # the project's shared media (chat + tickets), by handle
maia media -s "invoice" # search Maia's descriptions and filenames
maia media --download image-42 --to ./tmp
Every photo, video or PDF shared anywhere in the project has a handle like
image-42, the same one you see in chat. maia media lists them with
Maia's one-line description of each.
Tickets are addressed by key (MVP-42), bare number (42), or a title
fragment. A fragment matching more than one ticket is an error, never a
guess.
maia history shows who has had the ticket (the chain of custody, with how
and when it changed hands) and a timeline of everything that happened —
created, moved between stages, renamed, handed off, blocked/unblocked,
filed under a story, commented — each with who did it and from which
client. --json gives the same as {custody, timeline}.
Writing
maia new "Payment webhook retries" -d "Stripe retries land as duplicates." \
--assign priya --stage "In Progress" --story "Billing"
git log -1 --format=%B | maia new "Ship the retry fix" -d -
maia mv MVP-42 doing # a status…
maia mv MVP-42 "In QA" # …or a stage by name
maia assign MVP-42 priya
maia story MVP-42 "Billing" # file under an existing story
maia comment MVP-42 "Deployed to staging." --mention priya
maia comment MVP-42 - < notes.md
maia attach MVP-42 screenshot.png trace.pdf
maia attach MVP-42 --media image-42 # put EXISTING project media on the ticket
maia new "Webhook fix" -d "see the screenshot" --media image-42
maia drop MVP-42 --reason "superseded by MVP-51" # project admins only
--media <handle> works on new, attach, comment and edit. A
screenshot someone dropped in chat becomes the ticket's screenshot with no
download and re-upload. Media that's already on another ticket isn't moved:
the new ticket gets its own copy (its own bytes, a few seconds later), so the
same image can be on any number of tickets and nothing done to the original
later can reach into a ticket.
Every command takes --json for programmatic use, and -p CODE to act on
a project other than the selected one for that command. Both may come
before or after the subcommand. Progress notes ("uploaded x.png") go to
stderr, so --json stdout is always a single parseable document.
What it deliberately doesn't do
- No stage (column) create, rename or delete. Board structure is admin-curated in the app.
- No story create, edit or delete. Same reason. You can file a ticket
into an existing story with
maia story. - No ticket delete. The API has none.
maia dropsets the reversibledroppedstatus, which is what "scrap that" means on this board;maia mv <key> nextputs it back. Dropping is project-admin only from the CLI (the app lets any member), andmaia mv <key> droppedis held to the same rule.
For agents
maia agent-guide # a cheat-sheet written for an LLM
maia agent-guide --write CLAUDE.md # drop it into the repo (idempotent; re-run to refresh)
The guide tells the agent to use --json, never to run login itself,
how tickets are addressed, the exit codes, and which rules the CLI
enforces. With --json on, errors are JSON on stderr too
({"error", "hint", "exit_code"}), so failures parse as cleanly as
successes.
An agent on your machine shares your saved login. For a CI job or a shell on another box, hand it your identity for one run:
eval "$(maia auth env)" # exports MAIA_API_URL, MAIA_TOKEN, MAIA_TENANT_ID, MAIA_PROJECT
MAIA_TOKEN=$(maia auth token) # or just the token
Invoke-Expression (maia auth env | Out-String) # Windows; --shell cmd for cmd.exe
Exit codes
| code | meaning |
|---|---|
| 0 | fine |
| 1 | generic failure |
| 2 | not authenticated, or nothing selected |
| 3 | forbidden (e.g. you're an observer) |
| 4 | not found (a ticket, member, story, stage, project or workspace) |
| 5 | conflict; refetch and retry |
maia doctor exits 1 when any check fails, so a script can gate on it;
maia upgrade --check exits 1 when a newer release exists.
Metadata
Release files for maiactl 0.6.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| maiactl-0.6.2.tar.gz | 94.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| maiactl-0.6.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 147.8 kB
Release files / maiactl-0.6.2.tar.gz
| Download URL | maiactl-0.6.2.tar.gz |
|---|---|
| Size | 94.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8cdd8d51022836f206e48bd372494c3731e068cbe85e22d23cfc42b0512f381d
|
|
BLAKE2b-256 checksum How to use checksums |
9d21c2f6623c9101d25d09c58a4ab00aff123c96a2b6f44a69f7c527b1b60a1e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.5
|
Release files / maiactl-0.6.2-py3-none-any.whl
| Download URL | maiactl-0.6.2-py3-none-any.whl |
|---|---|
| Size | 53.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eec477d991e9d4d47837b038481cdba3e84d5bac1f393bb6a6503606a8d9baf0
|
|
BLAKE2b-256 checksum How to use checksums |
7111b7cbda2543e5f7d7627230434d9f198f73a26cc6ea26e45c1f05fbef7be1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.5
|