pastilhas 8260ac0a59 AGENTS.md, so both harnesses find the same guidance
opencode discovers AGENTS.md and nothing else; claude code discovers CLAUDE.md.
this repo had neither, so an agent sent to change something here arrived with no
guidance at all under either.

it deliberately does NOT repeat README.md. that file says what the plugin is,
for someone deciding whether to install it. this one answers the other question:
i have to change something in here, what will i break.

the facts in it are read from the repository rather than recalled — the panel
keys from web/panels.ts, the tables from db/schema.ts, the permission keys, the
host binaries and the route claims from manifest.ts. presence is the declaration
everywhere in this system, so the file says what the platform will actually
conclude from these files existing.

CLAUDE.md beside it is a pointer, not a copy: two copies drift and the stale one
is the one somebody reads.
2026-08-27 07:24:13 +00:00
2026-08-19 06:51:04 +01:00
2026-08-18 01:16:07 +00:00

Kiosk

Build, sign and install Officer's iOS and Android apps on your own registered devices. What EAS does, self-hosted.

  • A catalogue of builds, newest per app per platform, with an Install button that hands itms-services:// to iOS or a plain URL to Android.
  • A per-user key that authorises artefact downloads. Held in memory by the app and re-read on demand, so rotating one costs nothing.
  • Device records — who owns which iPhone or iPad, and whether Apple knows about it yet.

Status — read before installing

Nothing here has run. Ported on 2026-08-18 from the standalone service that was called OAS. The authenticated API and the two panels exist and typecheck; no endpoint has answered a request.

What works, in principle

GET /apps, GET /apps/:appName, GET /key, POST /key/rotate, GET /install/:appName/:platform/:version, POST /installs, and the device and build verbs. All under /api/kiosk, all requiring a platform session.

What is missing, and why

Installing will not work yet. Four requests in the flow arrive with no session — iOS installd fetching the manifest and the .ipa, the device posting its UDID, and Safari downloading a .mobileconfig — and a plugin cannot serve an unauthenticated route today. UNPROTECTED_API_PREFIXES is hardcoded in the platform and plugin routers mount only behind the account gate.

So GET /install/... returns a correct-looking URL pointing at a route that does not exist. That is deliberate: the URL shape is what the mobile app builds against, and it stops being a dead end the day the platform gains unauthenticatedPaths. Those routes then land in api/public.ts, kept separate from api/router.ts so the two surfaces cannot be confused by anyone reading either.

Enrolment is not ported for the same reason — it is entirely public routes.

Artefacts arrive by rsync, into $OFFICER_ROOT/data/kiosk/. The plugin records metadata about files already on disk; it does not accept uploads, because the platform's shared upload route caps at 5MB and no .ipa will ever satisfy that.

Notes

  • PUBLIC_URL must be HTTPS with a certificate iOS already trusts. It fetches the manifest and binary itself and declines silently otherwise, which looks exactly like a broken build.
  • Table names are prefixed kiosk_. There are no migrations — see the note at the top of db/schema.ts.
  • The plugin is currently appName: kiosk. If the plugin storefront takes that name, this one is renamed; see docs/kiosk-ecosystem.md in the platform.
S
Description
Officer Kiosk — build, sign and install iOS and Android apps on your own devices
Readme
2.2 MiB
Languages
TypeScript 100%