Files
13ft/CLAUDE.md
T
tonymandClaude Fable 5.1 71abf89784
Build / Build and release (push) Failing after 19s
feat: Sign in with OTM, health, changelog, tides beacon, Ideas — first OTM release (0.4.0)
app/otm.py is the Python port of pickup's ticket verifier (HS256, mandatory
exp, audience check, alg only read to reject, never raises) plus a signed
session cookie and an in-memory single-use jti guard. portable.py gates every
route but PUBLIC_PATHS behind a session and fails CLOSED (503) with no secret
— it is a fetch proxy. /api/health reports the version, /changelog renders
CHANGELOG.md, the footer carries version/sign-out/Ideas, tides beacon from
TIDES_HOST/TIDES_SITE_ID. Dockerfile: OTM labels + HEALTHCHECK. 17 tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014LCQmFvKshyAJsE8C3B5ET
2026-10-18 22:22:27 -05:00

66 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 13ft — OTM's fork
A self-hosted "read the article" fetcher (Python/Flask + gunicorn, ~200 lines
in `app/` (`portable.py` is the entrypoint)): it refetches a URL as Googlebot and serves the page
without the paywall/ad cruft. Forked 2026-10-14 from
[wasi-master/13ft](https://github.com/wasi-master/13ft) (the original project
was taken down once already — wasm-fyi → wasi-master — which is WHY we hold a
fork, not a dependency on GitHub/Docker Hub).
- **Remotes:** `gitea` = `tonym/13ft` on git.poweredbyotm.com (ours, push here);
`upstream` = GitHub wasi-master/13ft (pull only: `git fetch upstream && git
merge upstream/main`). Token for Gitea lives in `~/Projects/CLAUDE.md`.
- **Running today:** https://13ft.poweredbyotm.com on **anvil**, hand-provisioned
from the STOCK image `wasimaster/13ft:latest` (0.3.4) via
`platform/infra/13ft/setup-13ft-anvil.sh` (platform#91, closed) — a
`/srv/otm/clients/13ft/` compose stack + Caddy site with **basic auth**
(user `tony`; password root-only in `/srv/otm/clients/13ft/secrets/basic-auth.txt`).
Nothing in this repo is deployed yet.
## Where this is going (the reason for the fork)
Make it a proper **OTM first-party app** per
`platform/docs/first-party-app-guide.md` (house rules are CI-enforced by
`platform/test/first-party-house-rules.test.ts`):
1. **Build from this repo** — catalogue entry in
`platform/apps/otm-admin/lib/first-party-apps.ts` (`id: "13ft"`, repo
`tonym/13ft`, image `platform-13ft:latest`, port 5000, `hostname:
13ft.poweredbyotm.com`), then `build_app 13ft` / `deploy_app 13ft` via the
platform MCP. **First: destroy or rename the hand-provisioned `13ft` stack**
(same slug + hostname + container name) or the two will fight over the
Caddy site and the container name.
2. **Sign in with OTM** replaces Caddy basic auth: `needsAuthSecret` →
`OTM_SSO_SECRET`; add `/api/auth/otm-sso` (verify the HS256 ticket for aud
`app:13ft` — port `pickup/src/sso.js`, or in Python: HMAC-SHA256 over
`header.payload`, base64url, check `exp`/`aud`/`jti`, single-use) and a
session cookie; gate every fetch route on it. Grant-only (NOT
`publicAccess`) — a stranger's OTM account must not get a free fetch proxy.
3. **tides** (`needsAnalytics`) — the beacon `<script defer
src="${TIDES_HOST}/t.js" data-site="${TIDES_SITE_ID}">` in the page
template. **Ideas** (`needsSuggestions`) — a lightbulb that files Gitea
issues (see `pickup/src/ideas.js` for the plain-HTTP shape). Version +
`/changelog` + `/api/health` reporting the version + footer "Powered by OTM".
4. Keep the Dockerfile's non-root user (`thirteener`) and gunicorn; add a
`HEALTHCHECK`.
**Status 2026-10-18 (v0.4.0): steps 1–4 are built.** `app/otm.py` holds the ticket
verifier + session cookie + health/changelog/footer/Ideas; `portable.py` mounts the
`before_request` gate (everything but `otm.PUBLIC_PATHS` needs a session; no secret =
503, never open) and the routes. Tests: `cd app && ../.venv/bin/python -m unittest`
(pure `test_otm.py` + Flask test-client `test_app.py`). The platform catalogue entry
landed in otm-admin 0.148.0. Deploy = stop the hand stack (`cd /srv/otm/clients/13ft
&& docker compose down`), then `build_app 13ft` + `deploy_app 13ft`; grant `app:13ft`
to each reader on /users. The in-memory jti guard assumes gunicorn's single worker.
## Dev
```sh
python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
cd app && python portable.py # http://127.0.0.1:5000 (the app lives in app/)
docker build -t 13ft-dev . && docker run --rm -p 5000:5000 13ft-dev
```
Upstream README (below in this repo's `README.md`) covers the rest. Keep
upstream's MIT license file.