1name = "lmjtfy" 2main = "build/index.js" 3compatibility_date = "2026-09-01" 4# The site is https://lmjtfy.fun, bare (the owner's domain, on Cloudflare, 5# 2026-10-03). It began at https://lmjtfy.deizel.workers.dev, which stays on, 6# and www.lmjtfy.fun is attached, only so the Worker can send their 7# visitors, link previews and clones on with a permanent redirect (`gate` in 8# src/host.rs). No per-version preview addresses beside them. 9# 10# code.lmjtfy.fun is where the code is cloned from and read 11# (https://code.lmjtfy.fun/<repo>.git, the owner, 2026-10-05): the same Worker, 12# which gives that host the repositories and a front page listing them and 13# nothing else (src/host.rs), and sends every repository's path on 14# lmjtfy.fun, www and workers.dev there with a 308. 15workers_dev = true 16preview_urls = false 17routes = [ 18 { pattern = "lmjtfy.fun", custom_domain = true }, 19 { pattern = "www.lmjtfy.fun", custom_domain = true }, 20 { pattern = "code.lmjtfy.fun", custom_domain = true }, 21] 22 23[build] 24command = "worker-build --release" 25 26[vars] 27# Whether lmjtfy.fun, www and workers.dev send a repository's path to 28# code.lmjtfy.fun with a 308 ("on"), or serve the clone themselves as before 29# the code host ("off"). Exactly "on" or "off" (src/host.rs `GitRedirect`): 30# anything else is logged as an error and runs as "off". It is "on" since the 31# code host was checked (2026-10-05). To roll back, deploy once with 32# `--var GIT_REDIRECT:off`: the 308s are cached for a day (`max-age=86400`), so 33# a client that already followed one may keep going to the code host. 34GIT_REDIRECT = "on" 35# A pinned model: jev-protocol refuses aliases, because the answers behind an 36# alias change without a change here. 37JEV_MODEL = "jev-1.13.0" 38# The LLM that writes Jev's questions: the cheapest candidate that passed 39# every case of tools/eval (2026-10-02: 14/14, about 16 neurons a call; the 40# one cheaper model, granite-4.0-h-micro, passed 11). Rerun the eval before 41# changing it. 42LLM_MODEL = "@cf/qwen/qwen3-30b-a3b-fp8" 43# The most all visitors together may spend on Jev in a UTC day, in dollars. 44# The LLM's own budget is not set here: it is Workers AI's free allocation 45# (llm::FREE_NEURONS_PER_DAY). A dollar is about 25,000 requests at 46# $0.00004 each (the owner, 2026-10-02: "they will never hit it anyway"). 47JEV_DOLLARS_PER_DAY = "1" 48 49# Workers AI. There is no local emulation: `wrangler dev` calls the real 50# models on the real account, so it needs the account's token 51# (`lmjtfy-wrangler`, from the devshell). 52[ai] 53binding = "AI" 54 55# The day's budgets, shared by every visitor (src/meter.rs). 56[[durable_objects.bindings]] 57name = "BUDGET" 58class_name = "Budget" 59 60# Every request that was answered, and what people asked (src/archive.rs). 61# It also makes the calls, so the same request is never sent twice. 62[[durable_objects.bindings]] 63name = "ARCHIVE" 64class_name = "Archive" 65 66[[migrations]] 67tag = "v1" 68new_sqlite_classes = ["Budget"] 69 70[[migrations]] 71tag = "v2" 72new_sqlite_classes = ["Archive"] 73 74# The key is a secret, not a var. Declaring it makes `wrangler dev` read it 75# from the process environment (op-env-run puts it there) and makes 76# `wrangler deploy` refuse if the deployed Worker lacks it. 77[secrets] 78required = [ 79 "LMJTFY_TYPESAFE_API_KEY", 80 # For the account's real Workers AI usage (src/meter.rs): a token that can 81 # read analytics and nothing else, and the account it reads. A dev server 82 # without them counts only itself, and says so on the page. 83 "CLOUDFLARE_ANALYTICS_TOKEN", 84 "CLOUDFLARE_ACCOUNT_ID", 85 # For `git clone <site>/lmjtfy.git` (src/clone.rs): a GitHub fine-grained 86 # token with Contents: read on deizel/lmjtfy and deizel/jevcrates and no 87 # other permission. A dev server without it answers clones with 503. 88 "LMJTFY_GITHUB_TOKEN", 89] 90 91# Cloudflare's own record of what the Worker did (the owner, 2026-10-02): 92# every invocation logged, with what it logged, and every request traced 93# (fetches, Durable Object calls, the handler). Kept in the account's 94# dashboard: logs 3 days (Workers Free: 200,000 events a day), traces 7 days. 95# Traces count towards Cloudflare Observability billing from 2026-12-01; 96# lower `head_sampling_rate` before then if they would. 97[observability] 98enabled = true 99 100[observability.logs] 101enabled = true 102invocation_logs = true 103head_sampling_rate = 1 104 105[observability.traces] 106enabled = true 107head_sampling_rate = 1 108 109# ------------------------------------------------------- 110# Staging: the same code as its own Worker, `lmjtfy-staging`, with its own 111# archive and budgets, where a change is tried before it reaches visitors 112# (the owner, 2026-10-04: "a preview environment so we aren't pushing 113# straight to prod"). `lmjtfy-wrangler apps/lmjtfy deploy --env staging`. 114# 115# Only at staging.lmjtfy.fun, where Cloudflare Access admits the owner and 116# the agents' service token (nixos-config, access.nix). No workers.dev 117# address and no preview addresses: either would be a way round Access, and 118# every ask here spends real money. For the same reason there is no code host 119# here: a second address is a second way round Access, and nothing in 120# nixos-config's access.nix covers one. Staging serves the repositories itself, 121# where it is (src/host.rs, `Host::Other`), so the clone proxy and the code 122# pages are tried there; the code host's own routes are tried under 123# `wrangler dev` with a `Host: code.lmjtfy.fun` header (README, "The code 124# pages"). An environment inherits no bindings or 125# variables, so each is said again. 126# ------------------------------------------------------- 127[env.staging] 128name = "lmjtfy-staging" 129workers_dev = false 130preview_urls = false 131routes = [{ pattern = "staging.lmjtfy.fun", custom_domain = true }] 132 133[env.staging.vars] 134JEV_MODEL = "jev-1.13.0" 135LLM_MODEL = "@cf/qwen/qwen3-30b-a3b-fp8" 136# A tenth of production's: enough to try a change, not enough to matter. 137JEV_DOLLARS_PER_DAY = "0.10" 138 139[env.staging.ai] 140binding = "AI" 141 142[[env.staging.durable_objects.bindings]] 143name = "BUDGET" 144class_name = "Budget" 145 146[[env.staging.durable_objects.bindings]] 147name = "ARCHIVE" 148class_name = "Archive" 149 150[[env.staging.migrations]] 151tag = "v1" 152new_sqlite_classes = ["Budget", "Archive"] 153 154[env.staging.secrets] 155required = ["LMJTFY_TYPESAFE_API_KEY", "CLOUDFLARE_ANALYTICS_TOKEN", "CLOUDFLARE_ACCOUNT_ID", "LMJTFY_GITHUB_TOKEN"] 156 157[env.staging.observability] 158enabled = true 159 160[env.staging.observability.logs] 161enabled = true 162invocation_logs = true 163head_sampling_rate = 1