Skip to main content

xteink

Control Xteink e-ink readers privately. xteink is a local server with a file/book service, a web UI, an HTTP API and an MCP server, plus optional remote access through Cloudflare Tunnel and custom device firmware that joins Wi-Fi to sync books locally so reading works fully offline. Your library never leaves hardware you own: there is no cloud service, no account and no telemetry.

Status

What exists and runs today:

  • A local server (xteink.server) with a library, device registry, API keys and a device sync protocol (v1, docs/device-protocol.md).
  • A web UI served by the same server (upload, library, send to device).
  • An MCP server (xteink.mcp) so agents can push files and queue them for a device. See docs/mcp.md.
  • A Docker image and compose.yaml, with an opt-in remote profile for two Cloudflare Tunnels. See docs/remote-access.md.
  • The agent-first CLI (xteink server|library|device|tunnel|mcp ...) and the AgentCulture mesh identity, harness prompts, skill kit and CI/CD baseline.

What is not verified yet:

  • The device side. The firmware lives in a separate repo, agentculture/xteink-firmware (a CrossPoint Reader fork: theme, zoom mode, USB provisioning, pinned-root TLS, OTA from fork releases, the X3 key profile). On an Xteink X3 it is hardware-verified: a book pushed via MCP or the web UI reaches the reader over the home LAN, or over a phone hotspot through the tunnel, and reads with Wi-Fi off (see docs/evidence/). X4, X4 Pro and X4 Classic are build-verified only. Large (≥ 5 MB) EPUB syncs over HTTPS are not verified yet.

Architecture

 browser / curl / agents                      Xteink reader (firmware fork, pending)
        |                                              |
        | Bearer xtk_... (API key)                     | Bearer xtd_... (device key)
        v                                              v
 +--------------------------------------------------------------+
 | api container                                                |
 |   main app    :8780  web UI + /api/library, /api/devices,    |
 |                      /api/keys  (API key required)           |
 |   device app  :8781  /api/device/* only (device key)         |
 |   SQLite + files in /data (volume xteink-data)               |
 +--------------------------------------------------------------+
        ^
        | XTEINK_URL + each caller's key
 +--------------+
 | mcp :8782    |  streamable-http for LAN / tailnet / mesh agents
 +--------------+

 Optional (compose --profile remote, see docs/remote-access.md):
   cloudflared-ui      ebooks.culture.dev -> api:8780  (Cloudflare Access SSO)
   cloudflared-device  xteink.culture.dev -> api:8781  (device app only)

The main app and the device app are separate processes on separate ports on purpose. A tunnel points at port 8781 only, so remote exposure is limited by port: the device app does not mount any library, admin or docs route.

Quickstart

You need Docker with the compose plugin and git. Nothing else: the image builds the web UI and bundles pandoc (Markdown/HTML to EPUB).

git clone https://github.com/agentculture/xteink.git
cd xteink

# 1. Start the server (builds the image on first run; takes a few minutes).
docker compose up -d api

# 2. Mint the first API key. It is printed once and only its hash is stored.
docker compose run --rm api python -m xteink.server create-key admin
# -> xtk_...   (copy it now)

# 3. Open the web UI and connect this browser with that key.
#    http://<host>:8780   (use http://localhost:8780 on the same machine)

# 4. Upload a book or article in the UI, or via the API:
curl -H "Authorization: Bearer <api-key>" \
     -F "file=@my-article.md" -F "title=My article" \
     http://localhost:8780/api/library
curl -H "Authorization: Bearer <api-key>" http://localhost:8780/api/library

Uploads accept EPUB, BMP and TXT as-is, and Markdown/HTML converted to EPUB. PDF is not supported yet. See docs/api.md for every endpoint and error code.

Ports and settings

Copy .env.example to .env (cp .env.example .env) to change anything, or export the variables in your shell for one-off runs (for example a scratch stack on other ports: COMPOSE_PROJECT_NAME=scratch XTEINK_PUBLISH_PORT=18780 XTEINK_PUBLISH_DEVICE_PORT=18781 XTEINK_PUBLISH_MCP_PORT=18782 docker compose up -d api). The useful variables:

Variable Default Meaning
XTEINK_PUBLISH_PORT 8780 Host port for the web UI and main API
XTEINK_PUBLISH_DEVICE_PORT 8781 Host port for the device app
XTEINK_PUBLISH_MCP_PORT 8782 Host port for the MCP server
XTEINK_PUBLISH_BIND 0.0.0.0 Interface to publish on; 127.0.0.1 keeps it host-local
XTEINK_DATA_DIR named volume Host directory for the library (writable by uid 10001)
COMPOSE_PROJECT_NAME xteink Prefix for containers and the volume

Every port is protected by keys, including on the LAN. Binding to 0.0.0.0 exposes the web UI shell (not your data) to your network; set XTEINK_PUBLISH_BIND=127.0.0.1 if that is not what you want.

MCP for agents (optional)

docker compose up -d also starts the mcp service. It holds no key: mint one key per agent and give it to that agent's MCP client, which sends it as Authorization: Bearer <key>:

docker compose run --rm api python -m xteink.server create-key my-agent

The MCP endpoint is http://<host>:8782/mcp/ (with the trailing slash). A request without a key gets 401, and a revoked key is refused. See docs/mcp.md. MCP is for LAN, Tailscale and mesh use; it is never routed through the tunnel.

Remote access (optional)

docker compose --profile remote up -d adds two cloudflared connectors, one for the web UI behind Cloudflare Access and one for device-only sync. Tunnels, DNS and Access policy are provisioned separately with cultureflare. Behind Access the web UI needs no API key once XTEINK_ACCESS_TEAM_DOMAIN and XTEINK_ACCESS_AUD are set in .env. The full steps, including the hidden-secret handling for the tunnel tokens, are in docs/remote-access.md.

Provisioning a device (pending hardware verification)

The commands exist and are dry-run by default; the end-to-end flow has not been run on real hardware yet.

# 1. Back up the stock flash over USB first (reads only; dry-run without --apply).
uv run xteink device backup --port /dev/ttyACM0 --apply

# 2. Flash the firmware fork from agentculture/xteink-firmware (see that repo).

# 3. Mint a device key and send Wi-Fi, server URLs and the key over USB.
export XTEINK_URL=http://localhost:8780 XTEINK_API_KEY=<api-key>
uv run xteink device provision --port /dev/ttyACM0 --name my-reader \
    --lan-url http://<host>:8781 --apply

xteink here is the CLI from this repo (see Development below). Wi-Fi passwords are read from a per-user networks file outside the repo (--networks-file; xteink explain device provision shows the default location; keep it chmod 600, never commit it) or prompted, never passed as flags. The device speaks to the device app on port 8781 with its own xtd_ key; it can only download what you queued for it. Queue an item with xteink device queue or the web UI. See docs/device-protocol.md.

Local name

Containers do not do mDNS, so xteink.local is not provided by compose. See docker/avahi/ for running an Avahi publisher on the host, or use the host's IP or a DNS name.

Development

uv sync --extra server              # runtime + server + dev deps
uv run pytest -n auto               # test suite
uv run python -m xteink.server serve   # run both apps locally (data in the default data dir)
cd web && npm ci && npm run build   # build the web UI into xteink/server/_webassets

CLAUDE.md has the full command list, lint gates and conventions.

What you get

  • An agent-first CLI cited from teken (afi-cli) — the runtime package has no third-party dependencies.
  • A mesh identity — culture.yaml (suffix + backend) and the matching resident prompt file (CLAUDE.md, since this agent runs backend: claude). The mesh resident is one of two separate selections over this clone — see Two selections, not one below.
  • Four harness prompt files, one per agent harness, each read by exactly one of them (see Prompt files by harness below). All four harnesses are usable interactively regardless of which one culture.yaml names as the mesh resident.
  • The canonical guildmaster skill kit (19 skills) under .claude/skills/, vendored cite-don't-import. See docs/skill-sources.md.
  • A build + deploy baseline — pytest, lint, the agent-first rubric gate, and PyPI Trusted Publishing wired into GitHub Actions.

Prompt files by harness

Four harnesses, four root files, no shared base — each file is read by exactly one harness:

Harness File(s)
Claude Code CLAUDE.md
Pi / associate AGENTS.override.md + .pi/SYSTEM.md
colleague AGENTS.colleague.md
Qwen Code QWEN.md

Claude Code — CLAUDE.md is the fullest write-up of the repo's conventions; read it first.

Pi / associate — AGENTS.override.md replaces this directory's AGENTS.md/CLAUDE.md in Pi's context layer, so Pi does not inherit CLAUDE.md. .pi/SYSTEM.md replaces Pi's default system prompt with the non-coding associate identity (read/find/summarize only).

colleague — colleague's prompt cascade is AGENTS.md → AGENTS.colleague.md → AGENTS.colleague.<model>.md. This repo ships only the middle layer: there is no AGENTS.md (a shared base across harnesses was considered and rejected) and no per-model override file.

Qwen Code — Qwen Code reads QWEN.md and AGENTS.md; since there is no AGENTS.md, QWEN.md is its sole source of guidance.

There is intentionally no AGENTS.md at the root — each harness gets an unrelated file rather than cascading from a shared base.

Two selections, not one

It is tempting to read "switch harness" as one decision. It is actually two, and this repo keeps them separate:

  1. The interactive harness — which binary you run (claude, pi, colleague, qwen). cd into the clone and run any of them; all four are live simultaneously, and none of them requires editing a file or flipping a switch. A harness can be force-selected for one invocation (e.g. a CI smoke check) without ever touching culture.yaml — see docs/automation-contract.md.
  2. The mesh resident — the single backend culture.yaml declares, which is what the Culture daemon starts and what steward doctor checks. guild harness use <name> changes only this.

culture.yaml's backend affects (2) only. It never affects which harness you can invoke interactively in (1). See docs/harness-selection.md for the full writeup, including who reads this config and why existing siblings are not retrofitted by this arc.

Agent scaffold quickstart

uv sync
uv run pytest -n auto                 # run the test suite
uv run xteink whoami  # identity from culture.yaml
uv run xteink learn   # self-teaching prompt (add --json)
uv run teken cli doctor . --strict    # the agent-first rubric gate CI runs

CLI

Verb What it does
whoami Report this agent's nick, version, backend, and model from culture.yaml.
learn Print a structured self-teaching prompt.
explain <path> Markdown docs for any noun/verb path.
overview Read-only descriptive snapshot of the agent.
doctor Check the agent-identity invariants (prompt-file-present, backend-consistency).
cli overview Describe the CLI surface itself.
server status Probe the API, the key and the device app.
library list | add | rm List/search, upload, remove items. add and rm are dry-run unless --apply.
device list | queue | revoke | backup | provision Manage readers. Mutating verbs are dry-run unless --apply.
tunnel status | plan Probe the public hostnames; print the cultureflare commands (never executed).
mcp serve Run the MCP server (stdio, or --http).

The server, library, device and mcp nouns talk to the API using XTEINK_URL (default http://127.0.0.1:8780) and XTEINK_API_KEY.

Every command supports --json. Results go to stdout, errors/diagnostics to stderr (never mixed). Exit codes: 0 success, 1 user error, 2 environment error, 3+ reserved.

Contributing

See CLAUDE.md for the conventions:

  • Every PR bumps the version.
  • PRs go through the cicd lane.
  • Worktrees live in a repo-named folder.
  • Deploys use PyPI Trusted Publishing.

If you change project facts, update all four harness prompt files together.

License

Apache 2.0 — see LICENSE.

Metadata

Release files for xteink 0.10.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xteink 0.10.0
File Size Uploaded
xteink-0.10.0.tar.gz 1.7 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for xteink 0.10.0
File Interpreter ABI Platform
xteink-0.10.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.8 MB

Release files / xteink-0.10.0.tar.gz

Download URL xteink-0.10.0.tar.gz
Size 1.7 MB
Tags Source
SHA-256 checksum
How to use checksums
ae15e8edd6752c2df27509b64622086906cf9cb8993fb168a13a252e28309717
BLAKE2b-256 checksum
How to use checksums
cb50fdc1c32938a44288fd634d93ba50323a4d2101dcdacda6c83296202ebb6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / xteink-0.10.0-py3-none-any.whl

Download URL xteink-0.10.0-py3-none-any.whl
Size 87.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ddad0334e624f85478f3088851b49757b245f6f183f478c01c8b329004e0b0af
BLAKE2b-256 checksum
How to use checksums
4467b38efbcee0f58fc7062646d10cd1600bd9f5e5fe1bb4e0855ef3f764eea3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.12.0

2 release files

0.11.0

2 release files

This release

0.10.0 This release

2 release files

0.9.1

2 release files

0.9.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page