Metadata-Version: 2.4
Name: t4l-server
Version: 0.3.0
Summary: Self-hosted REST and MCP server for T4L Trainer agent workflows.
Project-URL: Homepage, https://github.com/BigSlikTobi/t4l-server
Project-URL: Repository, https://github.com/BigSlikTobi/t4l-server
Project-URL: Issues, https://github.com/BigSlikTobi/t4l-server/issues
Author: T4L Trainer
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: build>=1.3; extra == 'dev'
Requires-Dist: mypy>=1.18; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.14; extra == 'dev'
Requires-Dist: twine>=6.2; extra == 'dev'
Description-Content-Type: text/markdown

# T4L Server

Self-hosted REST and MCP server for exchanging T4L Trainer JSON artifacts
between the iPhone app and a coaching agent.

The phone remains the source of truth for accepted training state. The server
stores synced context and pending agent results in SQLite so local agents,
home-server agents, and private VPS agents can work through the same protocol.

## Install

```bash
pipx install t4l-server
t4l-server serve --data-dir ~/T4LServerData
```

The command prints the server URL, MCP URL, and API key. Enter the server URL
and API key in the T4L Trainer Settings screen. Pass `--log-level DEBUG` for
verbose request and error logging (logs go to stderr; the API key is only ever
printed to stdout on startup).

## Agent setup

Agents should read the public instruction repo first:

```text
https://github.com/BigSlikTobi/t4l-agent-instructions
```

Those instructions explain how to start or verify this server, connect through
MCP, wait for fresh phone context, and write app-consumed results safely.

## Safety model

- REST and MCP endpoints require the API key, compared in constant time.
- Request bodies over 25 MiB are rejected (413) to bound memory use.
- JSON artifacts are stored in `t4l.sqlite` (WAL mode; writes use an immediate
  transaction so concurrent upserts cannot interleave).
- Image blobs are stored under `blobs/`.
- Path traversal and arbitrary file access are rejected.
- Stored payloads are structurally validated before persisting.

## Architecture

Zero runtime dependencies by design — the server is built on the Python
standard library (`http.server`) so it installs and runs anywhere `pipx` does.
Keep it dependency-free.

- `constants.py`: single source of truth for artifact kinds, route maps, limits.
- `store.py`: SQLite artifact store (WAL, immediate-transaction upserts).
- `server.py`: REST handler, auth, body limits, logging.
- `mcp.py`: JSON-RPC MCP tools (read context / write results).
- `validation.py`: structural payload validation shared by REST + MCP writes.
- `cli.py`: `serve` entrypoint, logging, and the `chat-user` / `chat-agent`
  test clients (see below).

## Chat broker

The server also relays a live chat between the athlete (in the app) and the
coaching agent, replacing any third-party chat app. The conversation is an
append-only message log (its own `chat_messages` table, keyed by a monotonic
`seq` cursor — distinct from the latest-only artifact store). v1 is
message-level over polling; token/word streaming and SSE (`GET /v1/chat/stream`)
are reserved as future enhancements.

App side (REST, requires `x-t4l-token`):

| Method | Path | Body / query | Response |
|---|---|---|---|
| POST | `/v1/chat/messages` | `{"content": "..."}` (optional `conversationId`) | the stored message |
| GET | `/v1/chat/messages?since=<seq>` | — | `{"messages": [...], "latestSeq": <int>}` |

Agent side (MCP tools over the authenticated `POST /mcp`):

| Tool | Arguments | `structuredContent` |
|---|---|---|
| `get_pending_chat_messages` | `{}` | `{"messages": [...unanswered user turns...]}` |
| `write_chat_reply` | `{"content": "...", "inReplyToSeq": <seq>}` | `{"posted": {...assistant turn...}}` |

Posting a reply atomically marks the answered user turn(s) `answered`, so
`get_pending_chat_messages` only ever returns work the agent still owes.

### Test the round-trip without an app or an LLM

Two CLI simulators exercise the broker end to end in separate terminals:

```bash
# Terminal 1 — broker
t4l-server serve --data-dir ~/T4LChatTest --host 127.0.0.1 --port 8787 --api-key testkey

# Terminal 2 — agent simulator (auto-answers pending messages)
t4l-server chat-agent --server-url http://127.0.0.1:8787 --api-key testkey

# Terminal 3 — athlete/app simulator (interactive)
t4l-server chat-user --server-url http://127.0.0.1:8787 --api-key testkey
> How was my long run?
[user #1] How was my long run?
[assistant #2] You said: How was my long run?
```

`chat-agent --reply "<text>"` sends a fixed reply instead of echoing;
`chat-agent --once` answers the current backlog and exits; `chat-user --message
"<text>"` sends one message, waits for the reply, and exits.

### Browser test page

The server also serves a self-contained chat page at `GET /chat` for testing in
a browser instead of the `chat-user` CLI. With the broker and `chat-agent`
running (above), open:

```text
http://127.0.0.1:8787/chat?key=testkey
```

Type a message and the agent's reply appears within ~1–2s. The page is
same-origin with the API (no CORS) and sends your API key as `x-t4l-token`; the
`?key=` query just pre-fills the key field. It is a debug aid — the real client
is the app.

## Development

```bash
python -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
ruff format --check .
ruff check .
mypy
pytest
python -m build
twine check dist/*
```
