Skip to content

Releases: ddobrinskiy/LiveType

LiveType 0.1.3

Choose a tag to compare

@ddobrinskiy ddobrinskiy released this 04 Aug 12:31
5d384a2

One Worker can now serve several phones — your own and, say, a parent's — without handing your secret around, with each phone's spend tracked separately and a daily ceiling you can put on any of them.

Sharing a Worker

Each phone gets its own device secret, and the Worker decides which device a request came from by seeing which secret matched. Nothing the phone sends can claim an identity — the same rule that already keeps model choice server-side. So a lost phone is revoked on its own, without re-entering the secret on every other phone, and the usage ledger can say whose spend is whose.

Secrets are one variable per device, named DEVICE_SECRET_<NAME>DEVICE_SECRET_MOM holds the secret for the device mom, and that lower-cased suffix is the id the ledger and the app's Spending section show. OWNER_DEVICE_ID names the one device that sees every device's spend rather than only its own. A single-phone install needs neither: the existing DEVICE_SECRET keeps working, as the device default.

Keep the values in worker/.dev.vars; wrangler dev reads that file the same way the deployed Worker reads its secret store, so a local Worker gets the same devices with no extra configuration.

See Sharing the Worker.

A daily spend cap the Worker enforces

DEVICE_CAPS gives a device an allowance in USD per day, e.g. {"mom":1}. Once it is spent, POST /token refuses with 402 and the keyboard says so in the user's own language. The refusal happens before OpenAI is called, so there is no session and no charge — this is the first mechanism here that stops spend rather than recording it.

An uncapped device never runs the lookup, so it pays neither the extra database read nor its fail-closed behaviour. The allowance resets on the Worker's clock, set by CAP_TZ_OFFSET_MINUTES and defaulting to UTC — deliberately not the timezone the phone reports, since a device that chose its own day boundary could shift the window and hand itself a fresh allowance.

Two honest limits: the cap counts what the app managed to report, so a usage report lost to a dead network is not counted against it, and none of this is rate limiting. The OpenAI project budget remains the hard ceiling.

In the app

The Spending section now names the device the Worker recognised the phone as, shows its daily limit and what is left of it, and — on the owner's phone — breaks spend down per device. A device whose secret you later remove keeps its history there, marked revoked, because deleting a credential must not rewrite what was already spent.

Upgrading

Nothing to do on the phone. DEVICE_SECRET keeps working and its history is preserved: migration 0002 adds a device_id column whose default puts existing rows on the device default. Apply it with wrangler d1 migrations apply livetype-usage --remote before deploying, or POST /usage will fail on the missing column.

What is in the APK

Nothing of the maintainer's. Every BuildConfig default is empty in a release build, so the APK carries no Worker URL, no device secret and no vocabulary list. Verified against this artifact: zero occurrences of the maintainer's secrets, Worker hostname or dev endpoint in classes.dex.

Signed with the same LiveType release key as 0.1.2, SHA-256 f8:17:ef:58:2b:b7:d4:e3:c6:23:63:9f:9e:25:24:3b:68:80:13:eb:1b:ca:c3:1b:31:bf:cb:e9:c9:57:7b:b5, so it installs as an in-place update.

LiveType 0.1.2

Choose a tag to compare

@ddobrinskiy ddobrinskiy released this 03 Aug 11:33
14beccc

First release with an installable APK. Download LiveType-0.1.2.apk below and open it on the phone. No code changes since 0.1.1 — this release exists to publish a signed binary and the install guide that goes with it.

Installing

Read Install in the README. In short: deploy your own token Worker against your own OpenAI account, install this APK, then type the Worker URL and your device secret into the app. There is no hosted service and no account.

The README includes a paste-once prompt for a coding agent that does the whole Worker deploy for you.

What is in the APK

Nothing of the maintainer's. Every BuildConfig default is empty in a release build, so the APK carries no Worker URL, no device secret and no vocabulary list — a fresh install starts blank and is configured on device. Verified against the built artifact before upload.

Signed with the LiveType release key, SHA-256 f8:17:ef:58:2b:b7:d4:e3:c6:23:63:9f:9e:25:24:3b:68:80:13:eb:1b:ca:c3:1b:31:bf:cb:e9:c9:57:7b:b5. Later versions signed with the same key install as an in-place update; a build you sign yourself will not, so pick one source and stay with it.

Documentation fixes that came with it

  • worker/wrangler.jsonc pins a D1 database_id that only the maintainer's account owns, so wrangler deploy from a clone failed. Creating your own database and applying migrations is now step one.
  • OpenAI billing is spelled out: the API needs prepaid credit and a ChatGPT subscription does not include it. A key on a $0 account returns 429 on the first dictation.
  • A curl smoke test for /token that surfaces a quota or auth problem before the phone is involved.
  • The security model no longer reads as a blanket ban on distributing the app. Publishing this APK is fine; handing over a configured build is not, because that carries your device secret.

LiveType 0.1.1

Choose a tag to compare

@ddobrinskiy ddobrinskiy released this 01 Aug 16:46
5738864

Fixes found by putting 0.1.0 on a real phone. No binaries attached — build from source; the debug APK bakes in a device secret and deliberately never ships.

The indicator tap, broken twice over

Tapping a connection indicator did nothing, and it took two rounds to understand why, because there were two independent causes in the same window.

A negative margin — added to line the glyph up with the status text — put part of the tap target outside its parent. Android draws outside a parent when clipping is off, but it does not dispatch touches there, so the visible glyph had a dead strip.

That was fixed, and it still did nothing. The second cause: a Toast cannot be seen from a keyboard at all. TYPE_TOAST is window layer 7, TYPE_INPUT_METHOD is 13, so the IME draws over it; and a text toast is pinned 48dp from the bottom of the screen, which is now well inside the keyboard. It had genuinely worked earlier — the two height increases in 0.1.0 are what buried it. It is now a PopupWindow anchored to the glyph, which is a sub-window of the keyboard and therefore layered against it rather than under it.

Also fixed

The endpoint dropdown now persists the moment a mode is picked. Previously the choice only reached storage via Save, and a stored mode was silently coerced away while the production URL was blank — so once that URL existed, every visit snapped back to production.

Password managers no longer offer to save the device secret. The field was marked as a password input type, which is what makes an autofill service treat it as a credential; masking is now a display-only transformation.

The OpenAI indicator shows a spinner while its socket opens, instead of the red badge that means failure everywhere else — it had never been set to the connecting state at all.

Indicator glyphs are larger (26dp) with a 48dp touch target.

LiveType 0.1.0

Choose a tag to compare

@ddobrinskiy ddobrinskiy released this 01 Aug 16:31
5bd8199

First tagged release. No binaries attached — build from source (see the README); the debug APK deliberately never ships because it bakes in a device secret.

Keyboard

Dictation is live: the first transcript delta lands about a second into speech, not after you stop. The connection is prewarmed when the keyboard opens, and one session serves many phrases, so tapping stop does not cost a reconnect.

Thumb grid of paste, keyboard and mic over backspace and Enter. Enter inserts a newline mid-dictation without duplicating what was already transcribed; backspace repeats on hold and escalates to whole words; paste recovers the last phrase for five minutes, from memory only — nothing is written to disk.

The status line does not overclaim: it says "Ready" only once the socket is genuinely open. When another app takes the microphone — a screen recorder, a call — the keyboard says so instead of quietly streaming silence, and recovers by itself. Recording is capped at a configurable 1–20 minutes so a forgotten session cannot run up a bill.

Worker

The Cloudflare Worker holds the OpenAI key and hands the phone a 60-second ephemeral token; audio then goes phone-to-OpenAI directly and never transits the Worker. The Worker is the authority on model choice — the device may only send hints, which are clamped and filtered per model.

Spend is priced and aggregated in D1 in integer nano-USD, with each row freezing the price in force at the time, so a later price change never rewrites history. 55 tests run against a real D1 inside workerd.

Known limits

The phone must be in an OpenAI-supported country, because it talks to OpenAI directly — no server-side setting changes that. Release APKs are unsigned. The Worker has no rate limiting; the OpenAI account's spend cap is what bounds the risk.