tray
An in-tray for AI agents. An agent publishes a markdown or html document with one command and gets back a link; the owner opens the tray on a phone and finds everything their agents left for them, newest first, searchable, with a recycle bin.
pip install intray # the PyPI name is intray (PyPI reserves `tray`); the command is `tray`, the import is `intray`
tray publish report.md --session my-agent
# https://apps.example.com/tray/d/20261003-203301-quarterly-report-3f9a
Documents are plain files in a directory. The directory can be on this machine or on another one over ssh, and tray serve turns it into a private page. Nothing else is needed: no database, no build step, no API token.
For agents
Load the shipped skill, tray-publish, and you have the whole protocol: one command, one link, put the link in your reply.
tray publish report.md # one document, prints its link
tray publish a.md b.md c.html --group "Review" # one link for a set (the group link prints first)
tray publish ./artifact_dir # an html page with its assets, as one document
some-command | tray publish - --title "Log" # from stdin
tray ls "search words" # what is there, newest first
tray trash <id> / tray restore <id> # the recycle bin
--title overrides the inferred title (first # heading or <title>), --tags a,b adds searchable tags, --session name records who published. Markdown renders on the page with a toggle to the raw, copyable source; html renders as-is.
With pip install 'intray[mcp]', the same operations are an MCP server: python -m intray.mcp.
For the owner: where it goes and what link it prints
tray configure --target tw:/root/.local/share/tray --base-url https://apps.example.com/tray
That writes ~/.config/tray/config.toml. target is a directory, or host:/path for a directory on another machine reached by ssh host (keys, no prompt; host can be an alias from ~/.ssh/config). TRAY_TARGET and TRAY_BASE_URL override it per shell. With no configuration, documents go to ~/.local/share/tray and the link points at a local tray serve.
Hosting the page
pip install 'intray[server]'
TRAY_DATA_DIR=~/.local/share/tray tray serve --host 127.0.0.1 --port 8765
The page is at http://127.0.0.1:8765/tray/; its JSON API under /tray/api/. Put a reverse proxy in front for https. Auth is chosen by environment variables:
| Situation | Set | Effect |
|---|---|---|
Behind an existing login that exposes a who-am-I endpoint (enlace_auth's /auth/whoami) |
TRAY_WHOAMI_URL=http://127.0.0.1:8010/auth/whoami, TRAY_ALLOWED_USERS=me@example.com, optionally TRAY_LOGIN_URL=/auth/login |
the tray forwards the browser's cookies to that endpoint and allows the listed emails; anyone else is sent to the login page |
| No platform login | TRAY_BASIC_USER, TRAY_BASIC_PASSWORD |
HTTP Basic |
| Localhost only | nothing | open |
A systemd unit and a Traefik router for the first case are in misc/deploy/.
Python API
from intray import DocStore, publish
meta = DocStore("~/.local/share/tray").publish("report.md", tags=["q3"])
publish("report.md", tags="q3")["url"] # same, through the configured target
intray.api.mk_app(data_dir=..., authorizer=...) returns the FastAPI app for embedding.
Design notes
Flat store with tags and groups, not a hierarchy: agents from many projects do not share one, and every placement decision costs tokens. A document is docs/<id>/meta.json plus its files; the bin is trash/<id>/; a group is groups/<gid>.json. Ids are YYYYMMDD-HHMMSS-<slug>-<4 hex>, so a listing is already in time order and a URL says what it points to. The transport is a seam (tray.target): local directory, ssh, and later an http ingest endpoint. More in misc/docs/design.md.
Skills
gh skill install thorwhalen/tray tray-publish
The skill also ships inside the package at intray/data/skills/tray-publish/.
Metadata
Release files for intray 0.0.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 | |
|---|---|---|---|
| intray-0.0.2.tar.gz | 47.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| intray-0.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 91.6 kB
Release files / intray-0.0.2.tar.gz
| Download URL | intray-0.0.2.tar.gz |
|---|---|
| Size | 47.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1ae52456738ea921d0a4a297ae77f2fbea713229e132406ae1b1708f9ac4729d
|
|
BLAKE2b-256 checksum How to use checksums |
89543040ffd36f3cf7c1666894907b00aef1c88d5cbb9586daa9ad02650cf20a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","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 / intray-0.0.2-py3-none-any.whl
| Download URL | intray-0.0.2-py3-none-any.whl |
|---|---|
| Size | 44.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ccd57ae10977b9a01600b68dfc9dfd692e25fd2e0c3135a29ef4485d86fc61b4
|
|
BLAKE2b-256 checksum How to use checksums |
14be393dd9fd615e958b413b0a1c74b2c14c36ec687055477d1369040f5c6a0a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","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}
|