lmjtfy.git / apps / lmjtfy / wrangler.toml
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