Metadata-Version: 2.4
Name: taktcli
Version: 0.1.0
Summary: Authorized GraphQL CLI for Takt — full GraphQL surface from the shell, stdlib-only core
Author: Takt
License: MIT
Project-URL: Homepage, https://takt.sh
Project-URL: Repository, https://github.com/muzhig/takt
Keywords: takt,graphql,cli,agents
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Provides-Extra: watch
Requires-Dist: websockets>=12.0; extra == "watch"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"

# taktcli

Authorized GraphQL CLI for [Takt](https://takt.sh) — the full GraphQL surface
from the shell, for agents and humans. Package and console-script are both
named `taktcli` (bare `takt` is taken on PyPI).

The core is **stdlib-only** (`urllib` + `argparse` + `json`): `argv → GraphQL-over-HTTP`.
It sidesteps the MCP client-serialization bug class entirely — argv strings can't
be double-JSON-encoded. See PRD 002 in the Takt wiki (`takt/dev/prd-002-taktcli`).

## Install

```bash
pip install taktcli            # stdlib-only core
pip install 'taktcli[watch]'   # + websockets, enables `taktcli watch`
```

From a checkout (monorepo subdir):

```bash
pip install -e cli/
```

## Auth (PRD §6)

Resolution order, simplest-wins:

- **Token:** `TAKT_TOKEN` env → `~/.takt/credentials.json` → error `Run: taktcli login` (exit 3)
- **URL:** `TAKT_URL` env → `url` in the credentials file → default `https://api.takt.sh/graphql`

`~/.takt/credentials.json` is plain JSON (no YAML dependency):

```json
{ "token": "<jwt>", "url": "https://api.takt.sh/graphql" }
```

### Login

`taktcli login` runs the OAuth 2.0 device-authorization grant: it prints a
verification URL + user code, polls until you approve in the browser, then
writes `{token, url}` to `~/.takt/credentials.json` (mode `0600`).

```bash
taktcli login                                       # default endpoint
taktcli login --url http://localhost:8454/graphql   # override the endpoint
taktcli logout                                      # remove the stored credential
taktcli whoami                                      # nickname + claims + active source
```

`--url` (highest) → `TAKT_URL` env → default decides where `login` authenticates.
`whoami` reports which credential source is active (`TAKT_TOKEN` env vs the file).

## Exit codes (PRD §4.3)

| Code | Meaning |
|------|---------|
| `0` | success |
| `1` | GraphQL error (server reached; `errors[]` non-empty) |
| `2` | transport error (DNS/TCP/TLS/HTTP non-2xx/timeout) |
| `3` | config or usage error (no token, bad URL, unknown verb, missing flag) |

`extensions.code` (e.g. `LEASE_EXPIRED`, `ALREADY_CLAIMED`) is preserved in
error output so loops can branch on it.

## Shared I/O flags (PRD §4)

- `--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 envelope `{data, errors, extensions}`; default is pretty JSON of `.data`
- `--compact` — single-line JSON

Variable precedence (lowest → highest): `--vars-file` < `--vars` < `--var` < `--field-file`.

## Status

Implemented: **A** (foundation — package skeleton, transport, auth/URL
resolution, shared I/O plumbing, CI/release), **B** (`gql` + `schema`
passthrough), **C** (hot-path aliases + `task …`), **D** (`wiki …` +
`workspace …`), **G** (`login`/`logout`/`whoami`), and **H** (`label …`,
`notif …`, `project …`). Curated verbs each map to a GraphQL op via
`transport.execute`; the `wiki`/`workspace` verbs take a global `--wiki SLUG`
(default `$TAKT_WIKI`) and stream big markdown through
`--content-file`/`--old-file`/`--new-file` (`-` = stdin).

`label create` is CLI-only (the MCP server never wrapped `createLabel`, but the
GraphQL mutation exists). `label update`/`delete` are keyed by **name** and
resolve the id client-side via the `labels` query, mirroring the MCP tools.
`project rename` is name-only — slug changes are blocked on backend
[[takt-304]]. Per the [[takt-83]] guardrail there is **no** claims/permission
verb anywhere in the surface.

The remaining verb groups are stubs — running one exits 3 with a pointer to its
implementing subtask (E media/file, F watch, J delete-result).

## 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).
